Structs

Structs let you create custom types by grouping related data together.

Defining Structs

struct Point {
    x: i32,
    y: i32,
}

fn main() -> i32 {
    let origin = Point { x: 0, y: 0 };
    let target = Point { x: 3, y: 4 };

    @dbg(origin.x);  // prints: 0
    @dbg(target.y);  // prints: 4

    0
}

Structs in Functions

Pass structs to functions and return them:

struct Point {
    x: i32,
    y: i32,
}

fn distance_squared(p1: Point, p2: Point) -> i32 {
    let dx = p2.x - p1.x;
    let dy = p2.y - p1.y;
    dx * dx + dy * dy
}

fn main() -> i32 {
    let origin = Point { x: 0, y: 0 };
    let target = Point { x: 3, y: 4 };

    let dist_sq = distance_squared(origin, target);
    @dbg(dist_sq);  // prints: 25 (distance is 5)

    dist_sq
}

Nested Structs

Structs can contain other structs:

struct Point {
    x: i32,
    y: i32,
}

struct Rectangle {
    origin: Point,
    width: i32,
    height: i32,
}

fn area(rect: Rectangle) -> i32 {
    rect.width * rect.height
}

fn main() -> i32 {
    let rect = Rectangle {
        origin: Point { x: 10, y: 20 },
        width: 100,
        height: 50,
    };

    @dbg(area(rect));          // prints: 5000
    @dbg(rect.origin.x);       // prints: 10

    0
}

Mutable Struct Fields

If a struct variable is mutable, you can modify its fields:

struct Counter {
    value: i32,
}

fn main() -> i32 {
    let mut c = Counter { value: 0 };
    c.value = c.value + 1;
    c.value = c.value + 1;

    @dbg(c.value);  // prints: 2
    c.value
}

Move Semantics

By default, structs move when assigned or passed to functions. After a move, the original variable can't be used:

struct Point {
    x: i32,
    y: i32,
}

fn use_point(p: Point) {
    @dbg(p.x);
}

fn main() -> i32 {
    let p1 = Point { x: 1, y: 2 };
    use_point(p1);    // p1 moves here
    // use_point(p1); // ERROR: value already moved

    0
}

You cannot move a non-copy field out of a struct individually. To access individual fields as independent values, use destructuring (see below).

If you want a type to be copyable instead, mark it with @copy:

@copy
struct Point {
    x: i32,
    y: i32,
}

fn main() -> i32 {
    let p1 = Point { x: 1, y: 2 };
    let mut p2 = p1;  // p2 is a copy of p1

    p2.x = 100;

    @dbg(p1.x);  // prints: 1 (unchanged)
    @dbg(p2.x);  // prints: 100

    0
}

Struct Destructuring

To break a struct into its individual fields, use a destructuring let binding. This consumes the struct and binds each field to a new variable:

struct Point {
    x: i32,
    y: i32,
}

fn main() -> i32 {
    let p = Point { x: 10, y: 32 };
    let Point { x, y } = p;  // p is consumed

    x + y  // 42
}

All fields must be listed. Use _ to discard a field you don't need:

struct Pair {
    first: i32,
    second: i32,
}

fn main() -> i32 {
    let p = Pair { first: 42, second: 0 };
    let Pair { first, second: _ } = p;  // discard second

    first
}

You can rename fields during destructuring with field: new_name:

struct Point {
    x: i32,
    y: i32,
}

fn main() -> i32 {
    let p = Point { x: 3, y: 4 };
    let Point { x: px, y: py } = p;

    px * px + py * py  // 25
}

Destructuring is especially important for structs with non-copy fields, since you can't move individual fields out directly:

struct Names {
    first: String,
    last: String,
}

fn greet(name: String) {
    @dbg(name);
}

fn main() -> i32 {
    let n = Names { first: "Ada", last: "Lovelace" };
    // greet(n.first);  // ERROR: cannot move field `first` out of `Names`

    let Names { first, last: _ } = n;  // destructure instead
    greet(first);  // OK

    0
}