OxL Reference

Step 3. Data: data and enum

Why#

A good data model makes impossible states unrepresentable. OxL has two main building blocks for that:

  • data is a record: "this and that and the other" (a product of types). A user is a name and an age and an email.
  • enum is 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#

oxl✓ checked in CI
@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
dot

Walkthrough#

  • 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, Ord and Hash are also available.
  • In impl Point methods see the fields by name: x.abs() + y.abs(). A static fn origin() is called as Point.origin().
  • var fn moveBy is a method that changes the value; it can be called only on a var variable.
  • enum Shape { Circle(r: f64) … Dot } - variants may have fields or be "empty". They are created as Shape.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:

oxl✓ checked in CI
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#

  • data is "and", enum is "or"; methods live in impl.
  • @derive removes boilerplate.
  • Adding a variant to an enum breaks the build wherever a match does not handle it - this is protection, not inconvenience.

Next: pattern matching.

See also

OxL 1.0.0-beta · Documentation version 1.0.0-beta© OxwaldLink · generated from the Standard, the std sources and the guides