10. Structs, enums, and unions
10.1 Structs#
pub struct Style:
pub let color: Color = Color(0.0, 0.0, 0.0, 1.0)
pub let line_width: f64 = 1.0
var cache: Layout*?
pub struct Percentage:
let value: f64
pub func init(self, value: f64) -> unit!:
if value < 0.0 or value > 100.0:
error(percent.out_of_range, "percentage must be 0 through 100")
self.value = value
let opacity = try Percentage(75.0)
- Fields use
letorvarand are private to the module unlesspub. Declaration order is layout order (§5.11). - Without an
init, the compiler synthesises a memberwise initialiser,Style(color = ..., line_width = ...), positional or named, with field defaults filling omitted arguments. It is public when the struct and every required field are public. - A custom
init(self, ...)returnsunitorunit!, assigns every field exactly once, and cannot read a field or call a method before every field is assigned. Declaring one suppresses the memberwise form. ConstructionPercentage(75.0)producesPercentageorPercentage!. - A struct copies by value. There is no spread or update syntax; a mutable local updates
varfields, and immutable transformations are named methods. - A struct with all-hashable fields is equatable and hashable structurally (§7.4).
- A struct cannot contain itself directly; use a pointer.
10.2 Enums#
pub enum Direction:
north
east
south
west
pub enum Command:
open(path: str)
save(path: str)
resize(width: u32, height: u32)
quit
A case is written Direction.north or .north where the type is known. A payload is reached only by match. The tag and payload layout is the compiler's; ordinary code never sees an ordinal. Adding a case is a source-compatibility change because every exhaustive match must be updated, which is the point.
10.3 Integer-backed enums#
enum Flags as u32:
none = 0
readable = 1
writable = 2
executable = 4
An enum declared as an integer type with explicit values has that representation. (u32)flag reads it; Flags.from_value(value: u32) -> Flags? converts back and answers none for an unknown value. Bitwise operators are not defined on the enum; combine and test flags on the integer. Its C header form is §17.6.
Why. C enums are integers and are used as flags and array indices; refusing that is refusing C. Keeping the conversion explicit in both directions means a match on a Flags is still exhaustive and an unknown integer never becomes a Flags silently.
10.4 Unions#
union Value:
integer: i64
real: f64
bytes: u8[8]
A union stores one member at one address. Its alignment is its most-aligned member's, and its size is its largest member's size rounded up to that alignment. Reading a member other than the last written reinterprets the bytes, the rule of C11 §6.5.2.3 footnote 95. Because that read has no check, members are restricted to types with no invariant: integers, floats, usize/isize, nullable pointers, void*, extern structs, and arrays and structs of those. bool, enums, bare pointers, str, spans, and optionals are not admitted. A union is zeroed as bytes, may declare methods, and cannot implement interfaces or cross into full Luce.
Why. The tagged enum is the safe sum type. The raw union exists because C has it, C libraries expose it, and type punning through it is defined C. Restricting the member types is what keeps the reinterpretation from manufacturing a bool of 2 or a null in a non-null pointer.