Step 3. Data: data and enum
Why#
A good data model makes impossible states unrepresentable. OxL has two main building blocks for that:
datais a record: "this and that and the other" (a product of types). A user is a name and an age and an email.enumis a choice: "this or that" (a sum of types). A shape is a circle or a rectangle or a point, and every variant has its own fields.
Behaviour (methods) is written separately in an impl block, so the definition of the data stays short and easy to survey.
Example#
@derive(Eq, Show)
data Point { x: i32; y: i32 } // data is a record with named fields
impl Point {
static fn origin() -> Point => Point{ x: 0, y: 0 } // static fn works like a constructor
fn manhattan() -> i32 => x.abs() + y.abs() // fields are visible by name
var fn moveBy(dx: i32, dy: i32) { // var fn mutates the value
x += dx
y += dy
}
}
enum Shape { // enum is a sum of variants
Circle(r: f64)
Rect(w: f64, h: f64)
Dot
}
fn describe(s: Shape) -> str =>
match s {
Circle(r) => "circle r={r}"
Rect(w, h) => "rect {w}x{h}"
Dot => "dot"
}
fn main() io {
var p = Point{ x: 3, y: -4 }
print("{p.manhattan()}")
p.moveBy(1, 1)
print("{p}")
print("{p == Point{ x: 4, y: -3 }} {Point.origin() == p}")
for s in [Shape.Circle(1.5), Shape.Rect(2.0, 3.0), Shape.Dot] {
print(describe(s))
}
}Output
7
Point{x: 4, y: -3}
true false
circle r=1.5
rect 2.0x3.0
dotWalkthrough#
data Point { x: i32; y: i32 }- fields are separated by;or a newline. A record literal:Point{ x: 3, y: -4 }. All fields are required (unless a field has a default value).@derive(Eq, Show)asks the compiler to generate==and the conversion to a string{p}.Copy,OrdandHashare also available.- In
impl Pointmethods see the fields by name:x.abs() + y.abs(). Astatic fn origin()is called asPoint.origin(). var fn moveByis a method that changes the value; it can be called only on avarvariable.enum Shape { Circle(r: f64) … Dot }- variants may have fields or be "empty". They are created asShape.Circle(1.5).match s { … }takes a variant apart and extracts the fields. More in the next step.
Common mistakes#
A forgotten field in a record literal:
data User {
id: u64
name: str
}
fn main() {
let u = User{ id: 1 }
}error
missing-field
The diagnostic missing-field names the missing field. A typo in a field name gives
unknown-field.
A forgotten variant in a match (see the next step) is non-exhaustive: when you add a variant to an enum,
the compiler shows every place that must be updated.
Remember#
datais "and",enumis "or"; methods live inimpl.@deriveremoves boilerplate.- Adding a variant to an
enumbreaks the build wherever amatchdoes not handle it - this is protection, not inconvenience.
Next: pattern matching.