Structs, variants and enums
struct: a product type
struct Point { x: f64, y: f64 }
struct Wrapper<T> { value: T, count: i64 }
let p = Point { x: 1.0, y: 2.0 };
let q = Point { x, y }; // shorthand when a variable has the field's nameStruct fields are separated by ,; a trailing comma is allowed. A struct is a value type with a fixed layout. Every struct literal names its type; there are no anonymous object types. Two structs with the same fields are different types.
variant: a tagged union
A variant is a sum type: a value is exactly one of the cases listed, never two, and each case carries its own data.
variant Shape {
Circle(f64), // positional payload
Rect { w: f64, h: f64 }, // named payload
Empty, // no payload
}
variant Tree<T> { Leaf(T), Node(Box<Tree<T>>, Box<Tree<T>>) }The layout is { i32 tag, payload }, where the payload is a block the size of the largest case, the way a C union shares its members. What C does not have is the tag: the compiler writes it at construction and checks it before every read. There is no syntax that reads a payload without that check - a case is read only through match.
A case is constructed by name through its type, with the dot: Shape.Circle(1.0). There is no s.Circle on a value, and no :: anywhere.
Option<T> and Result<T, E> are variants, which is why Some(p), None, Ok(p) and Err(e) work as patterns everywhere.
enum: a numbered set
An enum is the other half: no case carries data, and each has an integer value. That is the C construct the word names, and the two do not overlap: a variant needs at least one case that carries data, an enum refuses any, and each refusal names the other word.
enum Errno { Interrupted = 4, NotFound = 2, Denied = 13 }
enum Color { Red, Green, Blue } // 0, 1, 2A case without a value takes one more than the previous; the first takes zero. Two cases may not share a value. The value is the tag, so an enum is one i32 and readable both ways:
let n = Errno.Denied as i64; // 13
let name = match Errno.of(2) {
Some(Errno.NotFound) => "not found",
Some(_) => "another case",
None => "unknown", // no case has that value
};E.of(n) answers Option<E>, never a cast: an arbitrary number is not a case, and the check is what keeps the type honest.
Recursion
A recursive type needs no word:
variant Tree { Leaf(i64), Node(Tree, Tree) }The compiler puts the edge that closes the cycle behind an indirection automatically, so the programmer writes the value and never sees the box. A mutual cycle gets the same treatment, and so does a ring of three types or more: the member that closes the cycle carries the indirection.
variant Expr { Num(i64), Neg(Stmt) }
variant Stmt { Eval(Expr), Skip }
fn depth(e: Expr): i64 {
match e {
Expr.Num(_) => 1,
Expr.Neg(s) => match s { Stmt.Eval(x) => 1 + depth(x), Stmt.Skip => 1 },
}
}
fn main(): i32 {
let e = Expr.Neg(Stmt.Eval(Expr.Num(3)));
println(`${depth(e)}`);
0
}A programmer who wants the indirection somewhere else writes Box<T> there.
Equality
== and != compare a value part by part, in declaration order, and stop at the first part that differs. The walk follows a part onto the heap: a string compares its characters, an Array its elements, a Box what it points at - which is how a recursive type gets an equality at all.
A type whose value is not its representation says so, with a pair:
struct Pair { a: i64, b: i64 } // a set of two numbers, so order is not value
impl Pair {
fn eq(self, other: Pair): bool {
(self.a == other.a && self.b == other.b) || (self.a == other.b && self.b == other.a)
}
fn hash(self): u64 { panic("the body") }
}The two come in a pair, and a type that declares one without the other is refused: a container that compares by one rule and hashes by another stores an entry under the first and looks for it under the second. Only == and != - there is no order on an aggregate.
A closure has no equality, and neither does an extern type handle or any value that holds one of them; comparing one is refused.
What every type gets
A struct, a variant or an enum the program declares has .clone(), ==, hash(x) and .debug() with nothing written: .debug() answers the value as text, P { x: 1, y: 2 } or Shape.Rect(1, 2). A type that holds something that cannot be cloned or compared lacks the operation that needs it.