§6 Type System
Primitive types:
| Type | Description | FFI C equivalent |
|---|---|---|
int |
64-bit signed integer (default for unsuffixed integer literals) | int64_t |
float |
64-bit IEEE-754 float (default for decimal literals) | double |
number |
Deprecated alias for float (kept for migration) |
double |
string |
UTF-8 string | — |
boolean |
Boolean | _Bool |
void |
No return value | void |
null |
Absence of a value | — |
Integer and float types (for FFI and low-level use):
i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64
Bare unsuffixed integer literals default to
int(i64); decimal or exponent literals default tofloat(f64). Thenumberkeyword is a deprecated alias forfloatretained for source compatibility. The compiler source and runtime prelude completed their migration to the explicitint/floatspellings in Slice E.3a (May 2026); the alias itself is scheduled for removal in Slice E.4 after the strict-refusal reapply (E.3b) ships — tracked on the roadmap.
Scalar coercion rules:
Scalar primitives fall into three kinds — int (i8/i16/i32/i64),
float (f32/f64), and boolean (i1). Coercion is permitted within a
kind (e.g. i32 → i64 widening) but rejected across kinds at value
boundaries — bindings, call arguments, struct fields, and arithmetic operands:
int ↔ floatmismatches are rejected; spell the conversion withas intoras float.boolean → int/boolean → floatmismatches are rejected. A boolean flowing into a numeric context (let n: int = b,b + 1, passingbto a numeric parameter) is not silently widened to0/1; the compiler emits an ABI primitive mismatch diagnostic with anas int/as floatfix-it. Convert explicitly —let n: int = b as int(zext) orlet x: float = b as float(uitofp) — when the numeric value of the boolean is intended. (as boolis not a valid cast — to derive a boolean from a number, use a comparison such asn != 0.) Boolean-to-string interpolation ("flag: ${b}") is unaffected: it renders"true"/"false", not a numeric widening.
Booleans are a distinct scalar kind, not narrow integers. This prevents the accidental, silent
bool → numberwidening that earlier partial-migration tooling tolerated. Theas intescape valve remains available where the 0/1 mapping is deliberate.
Composite types: structs, enums, arrays (T[])
Interfaces are method-only contracts: an interface member must be a method
signature (fn name(...) -> T). A data-field-shaped member (name: Type) is
a typecheck error (E0827); data fields belong on a concrete struct that
implements the interface:
interface Named { fn name() -> string; // OK — method-only contract // isAdmin: boolean; // E0827 — declare a struct for data fields}Object literals require a concrete struct target: data is constructed
only through a struct. A bare object literal { ... } resolves its shape
against the annotation or contextual type it targets; if that target is an
interface, or the binding is unannotated and no concrete struct can be
inferred, the literal is a typecheck error (E0828). The sanctioned path is
a struct annotation:
struct Person { name: string; }interface Named { fn name() -> string; }
let p: Person = { name: "Alice" }; // OK — concrete struct targetlet n: Named = { name: "Alice" }; // E0828 — Named is an interface, not a structlet u = { name: "Alice" }; // E0828 — no inferable struct targetOptional types: T? — the value is T or null
Union types: A | B — the value is either type; matched with match
Intersection types (A & B) are not a data type: the grammar accepts
A & B as a Type, but using it as a data/value type — a variable,
parameter, struct/enum field, or return-type annotation, or the RHS of a
type X = A & B alias — is a typecheck error (E0829). It is never
decomposed into a structural record of both interfaces’ members; the nominal
object model has no such structural type. A & B is reserved for generic
trait-bound composition — a bound is written +-separated (<T: A + B>, per
SFEP-0038), not with & — and stays undiagnosed in bound position; only
data/value-type uses are rejected. See SFEP-0039 for the full rationale.
interface Named { fn name() -> string; }interface Scoped { fn is_admin() -> boolean; }
type AdminUser = Named & Scoped; // E0829 — intersection at the alias definitionlet x: Named & Scoped = get_admin(); // E0829 — intersection in annotation positionGeneric types: user-declared generics (fn first<T>(items: T[]) -> T?).
Result<T, E> is on the roadmap; use union return types
(T | MyError) today.
Wrapper types (syntax accepted; enforcement planned for 1.0+):
| Type | Semantics |
|---|---|
Affine<T> |
May be dropped, cannot be duplicated |
Linear<T> |
Must be consumed exactly once |
PII<T> |
Personally identifiable information — egress guards planned |
Secret<T> |
Secret value — logging/serialization guards planned |
Reference types (syntax accepted; borrow checking planned):
&T— shared (read-only) borrow&mut T— exclusive mutable borrow
Raw pointer types (FFI, unsafe only):
*T— read-only raw pointer*mut T— mutable raw pointer*opaque— opaque foreign pointer (void*)