Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Structs

A struct is a named record with a fixed set of fields. Fields without a default are required. Structs are declared at the top level only and can be used before their declaration.

struct User { name, role = "member", tags = [] }

let ann = User { name: "ann" };
print(ann);          // User {name: "ann", role: "member", tags: []}
print(ann.name);     // ann
print(ann["role"]);  // member

let name = "bob";
print(User { name, role: "admin" }); // User {name: "bob", role: "admin", tags: []}

User { name } is shorthand for User { name: name }.

A default is a constant: a literal or operators on literals (60 * 60), a const, a variant of an enum of the file, an array or object of them, map() or set(). Each struct made gets its own copy of an array, object, map or set, so tags = [] is never shared:

struct User { name, tags = [] }

let mut ann = User { name: "ann" };
let bob = User { name: "bob" };
ann.tags.push("admin");
print(ann.tags); // ["admin"]
print(bob.tags); // []

A field that does not exist, or a required one left out, is a compile error in a literal:

struct User { name, role = "member" }
User { name: "ann", email: "ann@example.com" }; // Error at 'email': User has no field 'email'.
struct User { name, role = "member" }
User { role: "admin" }; // Error at 'User': Missing field 'name' of User.

Reading a field that does not exist is a runtime error:

struct User { name, role = "member" }
let ann = User { name: "ann" };
print(ann.email); // KeyError: User has no field 'email'.

Setting fields

Fields are read-only outside the struct’s own mutating methods: only self.x = ... (or self["x"], +=, ++, …) in a mut self method sets one. Anywhere else setting a field of a struct, through a variable, an element or another struct’s field, is a runtime error. To change a struct from outside, call one of its mut self methods or make an updated copy with spread.

struct User { name, role = "member" }
impl User {
  fun promote(mut self, role) { self.role = role; }
}

let mut ann = User { name: "ann" };
ann.promote("admin");
print(ann); // User {name: "ann", role: "admin"}

let mut team = [User { name: "bob" }];
team[0].promote("lead");
print(team[0].role); // lead
struct User { name, role = "member" }
let mut ann = User { name: "ann" };
ann.role = "admin"; // TypeError: Cannot set field 'role' of User outside its mut self methods.

A struct held in another struct’s field is changed by the outer struct’s methods, calling the inner one’s: self.pos.move(1) in a method of the outer struct; self.pos.x = 1 there, or acc.pos.move(1) outside it, is an error.

The rule is shallow: an array, object, map or set in a field is not part of the struct, so ann.tags.push("x") changes it in place.

Updating with spread

A literal may spread a struct of the same type, or an object of some of its fields, as T { ...t, x: 1 } does an update without changing t. Items apply in order, later ones winning, and a field none of them gives takes its default; one with no default, a key that is not a field, or a struct of another type is a runtime error.

struct Config { host, port = 8080, debug = false }

let base = Config { host: "localhost" };
print(Config { ...base, debug: true });  // Config {host: "localhost", port: 8080, debug: true}
print(Config { debug: true, ...base });  // Config {host: "localhost", port: 8080, debug: false}
print(Config { ...{ host: "example.com" } }); // Config {host: "example.com", port: 8080, debug: false}
print(base);                             // Config {host: "localhost", port: 8080, debug: false}

Converting

Calling a struct type converts to it. From an object the keys must be exactly the fields, the defaulted ones optional; a struct of the same type is returned as is, and any other value is an error. There are no implicit conversions, and none between struct types. This is how data from json.parse becomes a struct:

struct Config { host, port = 8080 }

let cfg = Config(json.parse(r"""{"host": "example.com"}"""));
print(cfg);      // Config {host: "example.com", port: 8080}
print(cfg.port); // 8080
struct Config { host, port = 8080 }
Config({ host: "example.com", timeout: 5 }); // KeyError: Config has no field 'timeout'.
struct Config { host, port = 8080 }
Config({ port: 80 }); // KeyError: Missing field 'host' of Config.

Type checks, equality, iteration

v is User is true only for a User, never for an object; see Type Tests.

== compares structs field by field and only if they are of the same struct, so a struct never equals an object. for k, v in s walks the fields in declaration order. object(s) copies a struct into a new object, object(s).len() its number of fields and type(s) its name.

struct User { name, role = "member" }
let ann = User { name: "ann" };

print(ann is User);                            // true
print({ name: "ann" } is User);                // false
print(ann == User { name: "ann" });            // true
print(ann == { name: "ann", role: "member" }); // false

for field, value in ann {
    print("{field}: {value}");
}
// name: ann
// role: member

print(object(ann)); // {name: "ann", role: "member"}
print(object(ann).len()); // 2
print(type(ann));   // User
print(type(User));  // struct