7. Data Structures
7.1 Structs
struct Point {
x Float,
y Float
}
struct Person {
name String,
age Int
}
-- Construction
let p = Point { x: 3.0, y: 4.0 };
let alice = Person { name: "Alice", age: 30 };
-- Positional construction (fields assigned in declaration order)
let p2 = Point{3.0, 4.0};
p2.x println; -- 3.0
-- Field access
p.x println;
alice.name println;
-- Nested structs
struct Line {
start Point,
end Point
}
let line = Line { start: p, end: Point { x: 6.0, y: 8.0 } };
line.start.x println;
7.2 Struct Update (Spread)
Create a new struct from an existing one, overriding specific fields with .... The spread source comes first, followed by the fields to override. The original struct is not mutated.
-- Start with p, override x
let p = Point { x: 1.0, y: 2.0 };
let p2 = Point { ...p, x: 10.0 };
p2.x println; -- 10.0
p2.y println; -- 2.0 (copied from p)
-- Copy all fields (clone)
let p3 = Point { ...p };
-- Start with fred, override two fields
struct Employee { name String, age Int, dept String }
let fred = Employee { name: "Fred", age: 30, dept: "Engineering" };
let fred2 = Employee { ...fred, age: 31, dept: "Management" };
fred2.age println; -- 31
fred2.dept println; -- Management
fred2.name println; -- Fred (unchanged)
fred.age println; -- 30 (original unmodified)
-- Works with nested structs
let line2 = Line { ...line, start: Point { x: 1.0, y: 1.0 } };
-- Works with template structs
struct Pair<A, B> { first A, second B }
let pair1 = Pair { first: 1, second: "hello" };
let pair2 = Pair { ...pair1, first: 2 };
pair2.second println; -- hello
-- Int-to-float promotion works with spread
let p4 = Point { ...p, x: 42 };
p4.x println; -- 42.0
7.3 Tuple Structs
Tuple structs have positional fields accessed by numeric index. They are useful for newtypes and lightweight wrappers.
-- Tuple struct declaration (parentheses, semicolon terminated)
struct Point(Float, Float);
struct Temperature(Float);
-- Construction uses function-call syntax
let p = Point(3.0, 4.0);
let temp = Temperature(98.6);
-- Field access by numeric index
p.0 println; -- 3.0
p.1 println; -- 4.0
temp.0 println; -- 98.6
-- Pattern matching on tuple structs
let Point(x, y) = p;
println(x + y); -- 7.0
match (temp) {
Temperature(v): v println; -- 98.6
}
-- Printing
p println; -- Point(3.0, 4.0)
temp println; -- Temperature(98.6)
7.4 Enums (Tagged Unions)
enum Shape {
circle Float,
rect (Float, Float),
point,
}
enum Result {
ok String,
error String,
}
enum Value {
integer Int,
floating Float,
text String,
nothing,
}
-- Construction
let s = Shape.circle(5.0);
let p = Shape.point;
let ok = Result.ok("success");
-- Pattern match to extract values
fn area(s Shape) Float {
match (s) {
Shape.circle(r): return 3.14159 * r * r;
Shape.rect(dims): return dims.0 * dims.1;
Shape.point: return 0.0;
}
0.0
}
Built-in Option<T>
Option<T> is a built-in template enum with cases some T and none. It is returned by map subscript and get(map, key). See Maps and Templates for details.
Error propagation with postfix try
The postfix try operator unwraps a Result<T, E> (from std.result) or an Option<T>: on ok(v)/some(v) the expression evaluates to v; on err(e)/none it early-returns the error from the enclosing function. The function's declared return type must be a Result with the same error type (or an Option). Like await, try sits in the postfix layer, so it binds tighter than any binary operator and chains mid-pipeline.
import std.result.*;
fn parsePositive(s String) Result<Int, String> {
match (parseInt(s)) {
Option.some(n): n >= 0 ? Result<Int, String>.ok(n)
: Result<Int, String>.err("negative: " $ s);
Option.none: Result<Int, String>.err("not a number: " $ s);
}
}
fn addParsed(a String, b String) Result<Int, String> {
let x = parsePositive(a) try; -- err returns immediately
let y = parsePositive(b) try;
Result<Int, String>.ok(x + y)
}
-- chains mid-pipeline: unwrap, then keep piping
fn doubled(s String) Result<Int, String> {
Result<Int, String>.ok(parsePositive(s) try * 2)
}
Inside a lambda, try returns from the lambda; inside an async fn, the early return resolves the function's Future. Mixing kinds (an Option-try in a Result-returning function), mismatched error types, and try in a function without a declared Result/Option return type are all compile-time errors.
7.5 Ordinal (Case Index)
-- ordinal returns the zero-based index of an enum case
enum Color { red, green, blue, }
ordinal(Color.red) println; -- 0
ordinal(Color.green) println; -- 1
ordinal(Color.blue) println; -- 2
-- Works with data-carrying cases too
ordinal(Option.some(42)) println; -- 0
ordinal(Option<Int>.none) println; -- 1
-- Works with template enums
enum Either<L, R> { left L, right R, }
ordinal(Either.left(42)) println; -- 0
ordinal(Either<Int, String>.right("hi")) println; -- 1
7.6 Tag (Case Name)
-- tag returns the case name of an enum value as a Symbol
enum Color { red, green, blue, }
tag(Color.red) println; -- 'red
tag(Color.green) println; -- 'green
tag(Color.blue) println; -- 'blue
-- Works with data-carrying cases too
tag(Option.some(42)) println; -- 'some
tag(Option<Int>.none) println; -- 'none
-- Works with template enums
enum Either<L, R> { left L, right R, }
tag(Either.left(42)) println; -- 'left
tag(Either<Int, String>.right("hi")) println; -- 'right