8Structs
A struct is a value type with named fields and (optionally) methods. Structs live on the stack, are passed by value, and are the right choice for plain data.
8.1 Declaration
type struct Point {
x: int;
y: int;
}
8.2 Fields and Visibility
Struct fields are public by default - readable and writable wherever the struct itself is visible. Restrict a field with private; a private field is then accessible only from the module that declares the struct (enforced as E0353). Calling a function-typed field, h.run(1), reads it, so a private one is refused outside that module as reading it is. Which module code is in is the module whose file it is written in, not the type it belongs to: a free function beside the struct may read the field, and an implement block written in another module may not, however it names the type. Visibility blocks group fields that share an access level:
type struct Rect {
private:
cached_area: int; // only code in Rect's own module may touch this
public:
width: int;
height: int;
}
A struct literal sets every field of the struct, including the ones it leaves to their defaults, so a literal may be written only where every field is visible. One private field and code outside the module cannot write the literal at all; it goes through a constructor the module provides. Setting a private field is the more dangerous operation, not the less: reading one leaks information, while setting one breaks the invariant the field exists to protect, so there is no exception for construction.
// module app::money
type struct Cents {
private:
n: i64;
public:
static of(n: i64) -> Cents { return Cents { n: n }; } // the module builds it
}
// module app::report
const c: Cents = Cents { n: 5 }; // E0353: field `n` of `Cents` is private
const d: Cents = Cents::of(5); // fine
Visibility may also be declared per-field with a leading private / public. Within a struct, only public: and private: blocks are valid; protected: is refused (E0117).
A method is declared in the module its declaration is written in: a private method in an implement block is private to the block's module, not to the module of the type it extends.
Field visibility (a
privatefield,E0353) is a different axis from a top-level type beingprivate(E0503- see section 13.4); both are scoped to the declaring module. Apublicstruct may haveprivatefields, and aprivatestruct's fields are public to the rest of its own module.
Fields may declare default values with = <expr>. When a struct literal
omits a field that has a default, the default is used; a field with no default
must still be supplied, and omitting one is E0355. A default is a standalone
expression - it cannot reference the other fields - and is evaluated afresh at
each construction that omits the field.
type struct Config {
debug: boolean = false;
verbose: boolean = false;
retries: i32 = 3;
}
const c: Config = Config {}; // -> { false, false, 3 }
const d: Config = Config { retries: 5 }; // -> { false, false, 5 } (supplied value wins)
// Omitting a field that has NO default is still `E0355`.
8.3 Methods
Methods are functions inside a struct body whose first parameter declares how the method takes the receiver:
&this: shared (read-only) borrow. The body may not modify fields.mut &this: exclusive (mutating) borrow. The body may modify fields.this/mut this: by-value (consuming) receiver. The receiver is moved into the method; the caller's value is consumed and may not be used afterward. Use this for a method that dismantles a value - unwrapping it into its parts, or handing its storage off.mut thisadditionally lets the body reassign the receiver binding.
type struct Rect {
width: int;
height: int;
area(&this) -> int {
return this.width * this.height;
}
scale(mut &this, factor: int) -> void {
this.width = this.width * factor;
this.height = this.height * factor;
}
}
The receiver shape is part of the signature, so a caller knows whether a method borrows or consumes the receiver without reading the body.
A method is reached only by calling it. r.area with no call, where area is a method and no field has that name, names no value and is refused (E0204), with a suggestion to write r.area().
Struct-destructuring bindings
A const/mut binding may use a destructuring pattern - { field, field, ... } with a type annotation naming the struct - to move a struct's fields out into individually named locals. Each field is moved into a like-named local (field order need not match the declaration). This is the idiomatic companion to a consuming (this) receiver: binding the fields marks the receiver as fully consumed, so its automatic drop is suppressed and the fields can be handed off without a double free. Any bound field you don't move onward is dropped normally at the end of scope.
type struct Box<T, A = GlobalAlloc> {
ptr: T*;
alloc: A;
// Consume the Box and return its raw pointer, transferring ownership
// to the caller. Destructuring `this` moves both fields out, so the
// Box itself is not dropped (that would free `ptr`); `alloc` is dropped
// at function end (a no-op for GlobalAlloc).
into_raw(mut this) -> T* {
const { ptr, alloc }: Box<T, A> = this;
return ptr;
}
}
8.4 Static Methods
A static method belongs to the type itself, not an instance. It is called with ::.
type struct Point {
x: int;
y: int;
static new(x: int, y: int) -> Point {
return Point { x: x, y: y };
}
static origin() -> Point {
return Point { x: 0, y: 0 };
}
}
const p: Point = Point::new(10, 20);
A struct has no constructor; a static new(...) factory is the idiom, and simply returns a struct literal.
8.5 Struct Literals
A struct literal names each field inside braces. Field order does not need to match declaration order, but every non-defaulted field must be specified.
const p: Point = Point { x: 10, y: 20 };
8.6 Generic Structs
type struct Pair<T> {
first: T;
second: T;
static new(a: T, b: T) -> Pair<T> {
return Pair { first: a, second: b };
}
swap(mut &this) -> void {
const temp: T = this.first;
this.first = this.second;
this.second = temp;
}
}
const ints: Pair<int> = Pair::<int>::new(1, 2);
const strs: Pair<string> = Pair::<string>::new("hello", "world");
Pair<int> and Pair<string> are independent types. See section 11.6.
8.7 Unions
A type union is an untagged, C-style union: all fields occupy the same storage, overlapping at offset 0. The union's size is that of its largest member and its alignment that of its most-aligned member.
type union Value {
i: i64;
f: f64;
bytes: u8;
}
Here sizeof(Value) == 8 - the size of the largest member (i64/f64), not their sum. Writing one member and reading another reinterprets the shared bytes:
mut v: Value = Value { i: 0 };
v.f = 1.5; // writes the 8-byte storage as an f64
const bits: i64 = v.i; // reads the same bytes back as an i64
A union is untagged: it carries no discriminant recording which member is active, so reading a member other than the one last written is the programmer's responsibility (the reinterpretation is well-defined; whether it's meaningful is not checked). When you want a tagged, exhaustively-checked sum type, use type enum instead.
Literals. A union literal initialises exactly one field; naming zero or more than one is a compile error (E0363):
const ok: Value = Value { i: 42 }; // OK
// const bad: Value = Value { i: 1, f: 2.0 }; // error E0363: exactly one field
Methods. Like structs, unions may declare methods inline (instance and static), or in an implement block:
type union Tagged {
raw: i64;
handle: i64;
static from_raw(n: i64) -> Tagged { return Tagged { raw: n }; }
get(&this) -> i64 { return this.raw; }
}
Generics. Unions may be parameterised:
type union Either<A, B> {
a: A;
b: B;
}
const e: Either<i64, f64> = Either::<i64, f64> { a: 100 };
Layout control. ![repr(c)] and ![align(N)] apply to unions exactly as they do to structs (see section 16).
Matching. A union value is not matched variant-wise the way an enum is (there is no discriminant). You match on a member's value - e.g. match (v.i) { 0 => ..., _ => ... } - and static match (T) works inside a generic union's methods.
Ownership. A union's members are never auto-dropped, since the active member is unknown. If a union owns a resource, implement Drop for it (implement trait Drop for Handle { drop(mut &this) -> void { ... } }); an inherent method merely named drop is not a destructor (section 15.2).
type struct Name { ... }
type enum Name { ... }
type trait Name { ... }
type union Name { ... }Begins a type declaration; the keyword that follows says which kind. On its own, type Name = Existing; declares an alias.
type struct Name {
field: Type;
method(&this) -> Ret { ... }
}A value type with named fields and optional methods. Structs live on the stack and are passed by value. Fields are public by default and may carry = default values; methods take their receiver as &this, mut &this, or this.
type struct PointPoint.x: intPoint.y: inttype struct Rectprivate type struct Name { ... }
private:On a field: only the declaring type's own methods may touch it (E0353). On a top-level item: confined to its own module, and naming a private type elsewhere is E0503. private: opens a block of such members.
Rect.cached_area: intpublic function name() { ... }
public:Visible to any module that imports this one - the default for top-level items and for struct fields, so it is mostly written to be explicit. public: opens a visibility block that groups the members after it.
Rect.width: intRect.height: inttype struct CentsCents.n: i64static name(param: Type) -> Ret { ... }A method that belongs to the type rather than an instance, called as Type::name(..). For structs, static new(..) returning a struct literal is the idiomatic constructor.
static Cents::of(n: i64) -> Cents(parameter) n: i64type struct Centsreturn value;Leaves the current function with the given value. A function whose return type is not void must return one; a bare return; is for void. In an async function it completes the future with the value.
Cents.n: i64(parameter) n: i64const NAME: Type = value;At module level, a true compile-time constant, conventionally SCREAMING_SNAKE_CASE. (mut at module level declares mutable global state instead.)
const c: Centsconst d: Centsstatic Cents::of(n: i64) -> Centstype struct ConfigConfig.debug: booleanfalseThe boolean literal. Booleans are a distinct 1-byte type, not interchangeable with integers; if and while conditions must be boolean.
Config.verbose: booleanConfig.retries: i32const c: Configtype struct Configconst d: ConfigConfig.retries: i32Rect::area(&this) -> int&this
mut &this
thisThe receiver of a method. Its first parameter declares how the method takes it: &this borrows read-only, mut &this borrows exclusively, and this / mut this move the value in and consume it. In the body, this.field reaches the members.
Rect.width: intRect.height: intRect::scale(mut &this, factor: int) -> voidmut &this
mut &TypeAn exclusive, mutating borrow. As a receiver, mut &this lets the method modify the fields - and callers can see that from the signature alone. As a parameter type, mut &T lets the callee mutate through the reference while the caller keeps ownership.
(parameter) factor: int(parameter) factor: inttype struct Box<T, A = GlobalAlloc>type parameter Ttype parameter Atype struct GlobalAlloc {}Zero-sized handle to the process-wide allocator. Construct with GlobalAlloc::new(); all instances dispatch through the same underlying backend.
Box.ptr: T*type parameter TBox.alloc: Atype parameter ABox::into_raw(mut this) -> T*mut thisA by-value receiver the body may also reassign. The receiver is moved into the method and consumed; the caller's value may not be used afterwards.
language referenceconst name: Type = value;An immutable binding: it cannot be reassigned after initialisation. The annotation may be omitted when an initialiser is present - the binding takes the initialiser's concrete type, inferred locally. Mutability is opt-in through mut.
public function alloc(size: u64, align: u64) -> void*Allocate size bytes aligned to align via GlobalAlloc. Null/zero guarded; returns null on OOM so the emitter's existing null-check on the old malloc result is unchanged.
type struct Box<T, A = GlobalAlloc>static Point::new(x: int, y: int) -> Point(parameter) x: int(parameter) y: inttype struct PointPoint.x: int(parameter) x: intPoint.y: int(parameter) y: intstatic Point::origin() -> Pointconst p: Pointstatic Point::new(x: int, y: int) -> Pointtype struct PointPoint.x: intPoint.y: inttype struct Pair<T>Pair.first: Ttype parameter TPair.second: Tstatic Pair::new(a: T, b: T) -> Pair<T>(parameter) a: T(parameter) b: Ttype struct Pair<T>Pair.first: T(parameter) a: TPair.second: T(parameter) b: TPair::swap(mut &this) -> voidconst temp: Tconst temp: Tconst ints: Pair<int>static Pair::new(a: T, b: T) -> Pair<T>const strs: Pair<string>primitive stringNUL-terminated raw string (u8*), matching the C ABI. Length-typed text is Str / String.
type union Name {
i: i64;
f: f64;
}An untagged, C-style union: every field shares the same storage at offset 0, so the size is that of the largest member. Writing one field and reading another reinterprets the bytes; there is no runtime tag.
language referencetype union ValueValue.i: i64Value.f: f64Value.bytes: u8mut name: Type = value;A mutable binding, reassignable after declaration - mutability is opt-in, const is the default. At module level it declares mutable global state. Before & it marks an exclusive reference or receiver instead.
mut v: Valuetype union ValueValue.i: i64mut v: ValueValue.f: f64const bits: i64const ok: Valuetype union TaggedTagged.raw: i64Tagged.handle: i64static Tagged::from_raw(n: i64) -> Taggedtype union TaggedTagged.raw: i64(parameter) n: i64Tagged::get(&this) -> i64type union Either<A, B>type parameter BEither.a: Atype parameter AEither.b: Btype parameter Bconst e: Either<i64, f64>type union Either<A, B>Either.a: Aimplement struct Name { ... }
implement trait Trait for Type { ... }
implement u64 { ... }Adds methods to a type outside its declaration. implement struct T (or enum, or a bare primitive) opens an inherent block; implement trait Tr for T supplies a trait's methods for one type. Resolution is at compile time - no dynamic dispatch.
// reservedOpens no form of the language: a declaration or member written with it is refused where it is written (E0117). A struct's fields and methods are public or private, and both are scoped to the declaring module.
function name(count: i32, args...)A variadic parameter: the trailing bucket receives any number of further arguments with C's calling convention, so the function stays ABI-compatible with the printf family. Wrap the bucket in VaArgs to read typed values from it.
type struct Pair<T>sizeof(T)The size of a type in bytes, as a compile-time constant - usable in static_assert. alignof(T) gives the alignment.
match (subject) {
Pattern(x) => { ... }
_ => { ... }
}The primary discriminator: branches on enum variants, integers, ranges, and destructuring patterns, and the compiler rejects a match that misses a case. There is no fallthrough. As an expression it evaluates to the chosen arm's value, so every arm must produce the same type.
pattern => { body }Separates a match arm's pattern from its body. Arms do not fall through.
_static match (T) { ... }Opens a compile-time match on a type parameter: the one arm whose type matches is kept during monomorphisation and the rest are discarded.
static match (T) {
u8 | u16 => { ... }
_ => { ... }
}Selects a body by inspecting a type parameter at compile time. The subject is a type and each arm is a type (| groups several; _ takes the rest). During monomorphisation the one matching arm is kept and the others discarded, so there is no runtime branch.
implement trait Trait for Type { ... }Introduces a trait implementation: the block supplies, for one concrete type, the method bodies the trait requires.
language referenceimplement trait Trait for Type { ... }Names the type a trait implementation is for. The block that follows supplies the bodies the trait requires.
language referencetype struct HandleHandle::drop(mut &this) -> void