Type System Basics
Jiang types are read left to right, from the inner value outward. Each suffix wraps the type before it.
Primitive Types
Section titled “Primitive Types”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.
Enum discriminants are stored as Int32 by default unless the enum declares another
integer representation.
Void is the zero-size type for functions with no meaningful returned data. Its only value is ().
String literals are UTF-8 byte sequences. Their default borrowed view type is UInt8[:0]&, so the
compiler can expose null-terminated data for C-style boundaries while keeping the slice length
separate from the sentinel byte:
String literals can still be assigned directly where the expected type is a plain UInt8[]&.
For non-literal sentinel slices, convert explicitly with slice() or a cast to UInt8[]&:
Type Suffixes
Section titled “Type Suffixes”Suffixes farther to the right wrap a wider layer:
Common suffix forms:
| Surface syntax | Meaning |
|---|---|
T? | optional value |
T^ | owning pointer |
T& / T&! | shared reference / unique mutable reference |
T* / T*! | readonly / writable raw pointer |
T[N] | fixed-size array |
T[N:S] | fixed-size array with sentinel value S |
T[] | unsized array type |
T[]& | borrowed slice |
T[]^ | owning slice |
T[:S]& | borrowed sentinel slice reference |
Always use these surface forms in declarations, extend targets, and @where type patterns. A
user-defined type named Option, Result, Ref, Box, or Slice is an ordinary nominal type and
receives no built-in behavior.
Type-level ! is deliberately narrow: it is valid only in T&! and T*!. Forms such as Int!,
T^!, T[]!, T?!, and T!& are invalid. Mutability for a variable, parameter, global, or field
is written after its binding name instead.
Type Inference
Section titled “Type Inference”Use _ as a placeholder for the right-hand side type. The compiler infers the initializer’s natural
type; add ! after the new name when that binding must be reassignable:
The declaration forms below keep type inference and binding mutability separate:
| Binding pattern | Meaning |
|---|---|
_ name | bind a new immutable value with the RHS type |
_ name! | bind a new mutable value with the RHS type |
Put ! after the name when the new binding itself must be reassignable:
Inference preserves the expression’s natural ownership/reference shape:
Binding mutability is not part of the value type, so copying from a mutable binding does not make the new binding mutable:
Ordinary variable declarations do not accept ref on the left. Create reference values explicitly
on the right:
Inside destructuring and patterns, ref versus ref! selects shared versus unique mutable borrow;
The type position after ref is optional: ref name means ref _ name, and ref! name means
ref! _ name. This applies to destructuring, payload patterns, and lambda captures:
Type patterns may infer only part of a type, as in Int[_] values or _[3] values.
Binding Mutability
Section titled “Binding Mutability”Put ! after a binding name to allow assignments to that storage location. This is metadata about
the binding, not part of the stored type:
Binding mutability does not enter a function signature. foo(Int value!) and foo(Int value) have
the same parameter type; the first form merely lets the function body reassign its local parameter.
By contrast, foo(Int&! value) requires a unique mutable reference, so that capability is part of
the signature.
There is no unique modifier. unique is an ordinary identifier; T&! alone expresses the
unique mutable capability.
For aggregate values, a member is writable only when both the outer access place and the member declaration provide write capability. A readonly outer view recursively freezes reachable fields.
Arrays and Slices
Section titled “Arrays and Slices”Array length is part of the type:
Nested arrays use repeated suffixes:
T[] is an unsized array type whose length is known at runtime. Bare T[] is not a normal by-value
type. Use T[]& for a borrowed slice reference:
T[]^ is the owned handle for an unsized array. It owns the initialized buffer, drops the elements,
and frees the allocation.
T[:S] is a sentinel unsized array type. Sentinel slice views such as T[:S]& carry an additional
type-level sentinel guarantee. A non-literal sentinel
slice does not implicitly lose that guarantee when assigned to T[]&; write value.slice() or
value$.as(T[]&) to request a plain slice view.
Pointers and References
Section titled “Pointers and References”T^ can auto-dereference when the expected type is T; use $.get() for an explicit pointee read.
T& and T* require explicit reads. Member access can pass through T^:
Optional
Section titled “Optional”T? means the value may be null:
Use optional chaining, ??, or .some(...):
Errorable Returns
Section titled “Errorable Returns”T@E is used in function and method return position for errorable results:
Use throw expr; to return the error side and try expr catch { ... } to handle it.