Skip to content

Type System Basics

Jiang types are read left to right, from the inner value outward. Each suffix wraps the type before it.

Bool flag = true;
Int count = -123;
UInt size = 123;
UInt8 byte = 255;
Int16 small = -45;
Float value = 12.3;
Double precise = 132.54;
Char ch = 'a';

Int and UInt are pointer-sized integers. Use fixed-width types such as Int32 or UInt64 when ABI, file, or protocol layout needs an exact width.

() is the Unit type. It is a zero-size value and is also used for functions with no meaningful returned data.

String literals are UTF-8 byte sequences:

UInt8[_] bytes = "jiang";
UInt8[] view = "jiang";

Suffixes farther to the right wrap a wider layer:

Int[2][3] matrix;
Int?[2][3] nullable_items;
Int?[2]?[3] nullable_rows;

Common suffix forms:

SyntaxMeaning
T!the current type layer is mutable
T?optional layer
T^owning heap pointer
T&non-owning reference
T*single-object raw pointer
T[]slice reference
T[*]many raw pointer
T[N]fixed-size array
T@Eerrorable function return type

Within one layer, ? must come before !: write Int?!, not Int!?, Int??, or Int!!.

Use _ to infer the type from the initializer:

_ answer = 42;
_ name = "Jiang";
_ values = [1, 2, 3];

Inference creates an immutable binding by default. Use _! for a mutable outer binding:

_! count = 0;
count = count + 1;

Inference preserves the expression’s natural type:

Int^ make_value();
_ owner = make_value(); // owner: Int^
Int copied = make_value()$.get();

! applies to the current type layer:

Bool! flag = true;
flag = false;
Int[3]! values = [1, 2, 3];
values = [4, 5, 6];
Int![3]! mutable_items = [1, 2, 3];
mutable_items[0] = 10;
mutable_items = [4, 5, 6];

For aggregate values, a member is writable only when that member’s own type is mutable.

struct User {
Int id;
Int! age;
}
User user = User { id: 1, age: 18 }
user.age = 19;
// user.id = 2; // error

Array length is part of the type:

Int[3] values = [1, 2, 3];
Int[_] inferred = [1, 2, 3];

Nested arrays use repeated suffixes:

Int[2][3] matrix = [[1, 2], [3, 4], [5, 6]];

T[] is a slice reference with runtime length:

Int[_] values = [1, 2, 3];
Int[] view = values[..];
Int^ owner = new Int(42);
Int value = owner$.get();
owner$.free();

T^, T&, and T* do not auto-dereference in ordinary value contexts. Use $.get() for explicit reads. Member access is the exception: owner.member can pass through T^:

Int value = 41;
Int& ref = value$.ref();
Int copied = ref$.get();
struct Box {
Int value;
}
Box^ box = new (Box { value: 42 });
Int n = box.value;

T? means the value may be null:

Int? maybe = 42;
Int? none = null;

Use optional chaining, ??, or is some:

_ field = user?.name;
Int value = maybe ?? 0;
if maybe is some payload {
return payload;
} else {
return 0;
}

T@E is used in function and method return position:

enum Err {
bad = 1,
}
Int@Err ok() {
return 42;
}
Int main() {
return try ok() catch () => 0;
}

Use throw expr; to return the error side and try expr catch (...) => fallback to handle it.