Skip to content

Search

Dokumentacja

Structs

Structs in Balaur Script: fields, defaults and optional fields, literals that name what differs, and how a struct copies as one value.

Na tej stronie

A struct groups named fields into one value. It has no methods, and it copies when assigned or passed, so it suits small data: a hit, a tile, a row of a table.

struct Hit {
    damage: int,
    source: str,
    tags: [str],
}

fn total(hits: [Hit]) -> int {
    let sum = 0;
    for h in hits {
        sum += h.damage;
    }
    sum
}

pub fn run() -> int {
    let a = Hit { damage: 3, source: "fire", tags: [] };
    let b = Hit { damage: 4, source: "ice", tags: ["cold"] };
    total([a, b])
}

// run() == 7

Defaults and optional fields

A literal names what differs from the declaration. A field left out takes its default, and an optional or any field left out is nil. Any other field has to be named.

pub struct Label {
    kind: str = "label",
    size: int = 3,
    text: str,
    tip: str?,
    data: any,
}

pub fn plain() -> str {
    let label = Label { text: "hi" };
    "{label.kind} {label.size} {label.text} {label.tip == nil} {label.data == nil}"
}

pub fn sized() -> int { Label { text: "x", size: 9 }.size }

// plain() == "label 3 hi true true"
// sized() == 9
struct Label { text: str }

pub fn broken() { let l = Label {}; }

// error: 3:27: missing field `text` of Label: name it, or declare its default in Label

A default is a literal: a number, a bool or a string. For a list or a vector, name it in the literal, or write a function that builds the struct.

struct Enemy { hp: int, path: [Vec2], at: Vec2 }

fn enemy(hp: int) -> Enemy {
    Enemy { hp: hp, path: [], at: vec2(0.0, 0.0) }
}

pub fn fresh() -> int {
    let e = enemy(5);
    e.path.push(vec2(1.0, 1.0));
    e.hp + e.path.len()
}

// fresh() == 6

Each field is named in full: Label { text } and Label { size: 1, ..other } do not compile.

A struct is a value

Assigning a struct copies its fields, and so does passing it. Writing to the copy leaves the original alone. A field written through a variable changes that variable's struct.

struct Inner { n: int }
struct Outer { inner: Inner, tags: [str] }

fn weaken(o: Outer) { o.inner.n = 0; }

pub fn run() -> str {
    let a = Outer { inner: Inner { n: 1 }, tags: [] };
    let b = a;
    b.inner.n = 5;
    a.inner.n += 1;
    weaken(a);
    b.tags.push("t");
    "{a.inner.n} {b.inner.n} {a.tags.len()}"
}

// run() == "2 5 1"

A copy is shallow. a and b hold their own numbers and share the one tags list, since a list is shared by whatever holds it.

To let a function change the caller's struct, use an &mut parameter.

Comparing and printing

== compares two structs field by field, and "{s}" writes one as a map of its fields.

struct P { x: int, name: str }

pub fn same() -> bool { P { x: 1, name: "a" } == P { x: 1, name: "a" } }
pub fn shown() -> str {
    let p = P { x: 1, name: "a" };
    "{p}"
}

// same() == true
// shown() == "{\"x\": 1, \"name\": \"a\"}"

What a struct refuses

A struct cannot contain itself by value, since it would never end. Through a list it can. impl does not apply, so a function takes the struct instead, or the struct becomes a class.

struct Tree { kids: [Tree] }

pub fn run() -> int {
    let t = Tree { kids: [Tree { kids: [] }] };
    t.kids.len()
}

// run() == 1
struct A { a: A }

// error: 1:1: struct A contains itself through `a`; put it in an array, or make it a class
pub struct S { x: int }
impl S { fn m() -> int { 1 } }

// error: 2:10: `impl S`: a struct has no methods; make it a `class`, or write a function that takes it