Structs
Define, construct, access, mutate, and transfer nominal struct values in Prismio 0.1.
Last verified
A struct declares named fields and creates a nominal, move-only type. Structs group related values without introducing inheritance or an object runtime; methods are attached separately, in impl blocks.
struct Point {
x: Int,
y: Int
}
fn main() -> Int {
let point = Point { x: 4, y: 9 }
point.x = point.x + 1
return point.x + point.y
}Declaration syntax
Fields are written as name: Type entries inside braces and separated by commas.
struct Request {
id: U64,
retry_count: Int,
path: String
}The declaration introduces Request into the program's type namespace. Field order contributes to the current compiled layout, but source code should use names rather than assuming offsets. A stable cross-version struct ABI is not promised in 0.1.
Fields may contain scalars, enums, fixed-length arrays, vectors, strings, other structs, and permitted optional types. An array field is written Array<T, N> and stored inside the struct — see returning and storing arrays. A directly recursive stored field has no finite layout; model recursive links with an optional reference-shaped struct field, as shown on the optional values page.
Visibility
A struct and each of its fields carry a visibility marker, written before the name, with the levels a function has: public is everywhere, internal the declaring package, private the declaring file. A struct is internal unless marked, and a field is private unless marked, so a type is a closed box until its author opens it.
public struct Point {
public x: Int,
internal tag: Int,
secret: Int
}Point is nameable from any file, x is readable and writable from any file, tag from files in the same package, and secret only from the file that declares Point. Reading or writing a field you cannot see, or naming a struct you cannot see, is a compile error that says which level stops you and what to write. A word is a marker only when a name follows it, so a field called public still reads as one.
A struct literal sets every field, the written ones and the ones left to their defaults, so it is written only where all of them are visible. A struct with a hidden field is built by a function its own file provides, as Buffer is built by Buffer(16).
Construction
A struct literal names its type and provides field initializers:
let request = Request {
id: 42,
retry_count: 0,
path: "/health"
}Every declared field must receive a compatible value. Initializers are matched by field name, not by an anonymous positional tuple. A misspelled, missing, or unknown field is a compile-time error.
Struct literals are expressions and may be returned, passed to functions, or nested inside another literal. Their ownership behavior follows the destination: an owned move-only field transfers into the new struct.
Nominal identity
Two declarations with identical fields remain different types.
struct ScreenPoint { x: Int, y: Int }
struct MapPoint { x: Int, y: Int }
fn main() -> Int {
let screen = ScreenPoint { x: 1, y: 2 }
let map: MapPoint = screen
return map.x
}The compiler does not perform structural conversion. Write an explicit conversion function that constructs MapPoint if the distinction is intentional.
Field access and assignment
Fields are selected with . and initialized by name in a struct literal. Every declared field must have a compatible initializer. Two structs with the same field shapes remain different types.
Field access reads the declared field type:
let attempts: Int = request.retry_countField assignment requires an assignable value of that field type. Compound assignment on a field is not implemented as a general assignment place; spell the operation explicitly:
request.retry_count = request.retry_count + 1Prismio 0.1 permits field assignment through a struct binding even when that binding was not declared mut; mut is enforced for direct binding reassignment. This distinction is current compiler behavior and may be tightened in a later language version.
An optional struct must be unwrapped before member access. Comparing it with none does not flow-narrow the expression; call expect and access the result.
Ownership
Struct values move when stored into another owned location or passed to sink. Ordinary function parameters borrow them. Methods are attached with an impl block and a type satisfies a bound with impl <Trait> for <Type>. Constructors, field visibility modifiers, and inheritance are not implemented.
struct Ticket { number: Int }
fn number(ticket: Ticket) -> Int {
return ticket.number
}
fn close(sink ticket: Ticket) -> Int {
return ticket.number
}
fn main() -> Int {
let ticket = Ticket { number: 9 }
let observed = number(ticket)
return close(ticket) - observed
}number borrows, so the caller can later pass ticket to the consuming close. After close, the binding is moved.
When a struct contains another move-only value, constructing the outer struct transfers the inner value. Reading a scalar field from a borrowed struct is safe; transferring an owned field out of a borrowed parameter is rejected where it would violate the borrow.
Current limitations
- Methods and associated functions are written in
implblocks, inherent or for a trait, and animplmay be generic. There is no dedicated constructor declaration: write an associatednewthat returns the struct, andPoint(3, 4)calls it — see calling a type. - Visibility applies to a struct and its fields. An enum takes the same markers, without fields: see enum visibility.
- Structs may be generic; there are no field defaults. A literal that omits a field is accepted, and the omitted field is zero-initialised — a null pointer, a zero number, zeroed array elements, or a recursively zeroed inline struct.
- A generic struct cannot hold an
Array<T, N>field yet. - There is no update syntax that copies unspecified fields.
- Layout and cross-version ABI stability are not guaranteed.
- Field assignment through an immutable binding is currently accepted and may be tightened.
Use standalone functions for behavior and keep foreign-facing layouts behind a small FFI layer.