Structs
Define your own value types with named, typed fields, give them methods, and load them from JSON.
Objects are flexible, but nothing stops you misspelling a key or putting text where a number belongs. A struct fixes the shape: it names each field and gives it a type and a default, and the compiler checks every use.
struct Point (
int x = 0
int y = 0
)
Point origin = Point()
Point p = Point(3, 4)
log origin.x origin.y
log p.x p.y
log pCall the struct's name like a function to make one. With no arguments, every field takes its default. With arguments, give one per field, in order.
Named fields#
Pass fields by name to set only some of them, in any order. Braces do the same thing:
struct Window (
string title = "Untitled"
int width = 800
int height = 600
boolean resizable = true
)
Window w = Window(title = "Editor", resizable = false)
log w.title w.width w.height w.resizable
Window small = Window{width: 320, height: 240}
log small.title small.widthThe compiler knows the shape#
Because every field is declared, a typo or a wrong type is caught before the program runs:
struct Point (
int x = 0
int y = 0
)
Point p = Point()
p.z = 5struct Point (
int x = 0
int y = 0
)
Point p = Point()
p.x = "left"Methods#
A struct can have functions of its own, called methods. Inside a method, self is the struct it was called on:
struct Rect (
number width = 0
number height = 0
def area() number -> self.width * self.height
def isSquare() boolean -> self.width == self.height
def scaled(number by) Rect -> Rect(self.width * by, self.height * by)
)
Rect r = Rect(3, 4)
log r.area()
log r.isSquare()
log r.scaled(2).area()Copies, not shares#
Arrays, objects and classes are shared when you assign them. A struct is a value, like a number: assigning it, passing it to a function or storing it somewhere makes a copy. Changing the copy leaves the original alone:
struct Point (
int x = 0
int y = 0
)
Point a = Point(1, 1)
Point b = a
b.x = 99
log a.x b.x
def moved(Point p) Point (
p.x += 10
return p
)
Point c = moved(a)
log a.x c.xThis makes structs safe to pass around: no function can change your struct behind your back. Two structs with the same field values are equal:
struct Point (
int x = 0
int y = 0
)
log Point(1, 2) == Point(1, 2)
log Point(1, 2) == Point(2, 1)Fields that may be empty#
A field's type can end in ? to allow null. Fields can also hold arrays, maps and other structs:
struct Address (
string city = ""
string country = ""
)
struct Person (
string name = ""
string? nickname = null
string[] languages = []
Address home = Address()
)
Person ada = Person(name = "Ada", languages = ["English", "French"], home = Address("London", "UK"))
log ada.nickname ?? ada.name
log ada.languages.len
log ada.home.cityLoading structs from JSON#
Structs are the natural shape for data from files and APIs. json.parse<Point> checks the JSON against the struct and gives you a real Point, with the compiler's checks from then on. It returns a result, because the JSON might not fit:
import "std:json"
struct User (
string name = ""
int age = 0
boolean admin = false
)
User u = json.parse<User>(`{"name": "Grace", "age": 85}`).unwrap()
log u.name u.age u.admin
switch json.parse<User>(`{"name": "Bob", "age": "old"}`) (
case ok(user)
log user.name
case err(e)
log "rejected: " ++ e.kind
)Missing keys take the field's default, keys the struct doesn't declare are ignored, and a value of the wrong type is an error that names the struct and field. Going the other way, log and json.stringify turn a struct into JSON.
Structs without a name#
For a one-off shape, write the fields inline where a type goes:
def bounds() struct(int w, int h) (
return struct(w = 640, h = 480)
)
auto size = bounds()
log size.w * size.hPoint a = Point(1, 1), then Point b = a and b.x = 5. What is a.x?
Shopping basket
Give Item a total method that returns its price times its quantity, then print the basket's total.
14.25