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 within the declaring type's own methods (enforced as E0353), so it is hidden even from free functions in the same module. Visibility blocks group fields that share an access level:
type struct Rect {
private:
cached_area: int; // only Rect's own methods may touch this
public:
width: int;
height: int;
}
Visibility may also be declared per-field with a leading private / public. Within a struct, only public: and private: blocks are valid; protected: is reserved for classes (where it extends access to subclasses). Class members carry no default - every field and method must appear inside an explicit visibility block.
Field visibility (a
privatefield -> type-scoped,E0353) is a different axis from a top-level type beingprivate(module-scoped,E0503- see section 14.4). 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.
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);
For structs there is no new keyword; static new(...) is the idiomatic constructor 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 12.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 17).
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 is treated as a plain-data (Copy) value: its members are never auto-dropped, since the active member is unknown. If a union owns a resource, give it an explicit drop method and that is honoured.