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 and Enums

Structs

A struct groups related data together:

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

fn main() {
    let origin = Point { x: 0.0, y: 0.0 };
    let p = Point { x: 3.0, y: 4.0 };

    println(f"({p.x}, {p.y})");
}

Struct names are Type-class identifiers (PascalCase); field names are Value-class (snake_case). The compiler enforces both — see Naming identifiers in chapter 2.

Methods on structs

Use impl blocks to attach behavior:

struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    fn area(ref self) -> f64 {
        self.width * self.height
    }

    fn is_square(ref self) -> bool {
        self.width == self.height
    }

    fn new(width: f64, height: f64) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let r = Rectangle.new(10.0, 5.0);
    println(f"Area: {r.area()}");
    println(f"Square? {r.is_square()}");
}

Tuple structs

For lightweight wrappers:

struct Meters(f64);
struct Seconds(f64);

These are distinct types — you can't accidentally pass Meters where Seconds is expected.

Enums

An enum defines a type that can be one of several variants:

enum Direction {
    North,
    South,
    East,
    West,
}

fn describe(d: Direction) -> String {
    match d {
        Direction.North => "going up",
        Direction.South => "going down",
        Direction.East => "going right",
        Direction.West => "going left",
    }
}

Enums with data

Variants can carry data — this is what makes Kāra enums algebraic data types:

enum Shape {
    Circle(f64),                    // radius
    Rectangle(f64, f64),            // width, height
    Triangle { a: f64, b: f64, c: f64 },  // named fields
}

fn area(shape: Shape) -> f64 {
    match shape {
        Shape.Circle(r) => 3.14159 * r * r,
        Shape.Rectangle(w, h) => w * h,
        Shape.Triangle { a, b, c } => {
            let s = (a + b + c) / 2.0;
            (s * (s - a) * (s - b) * (s - c)).sqrt()
        }
    }
}

Option and Result

Two enums are so fundamental they're in the prelude — available everywhere without import:

enum Option[T] {
    Some(T),
    None,
}

enum Result[T, E] {
    Ok(T),
    Err(E),
}

Option represents a value that might not exist. Result represents an operation that might fail. You'll use them constantly:

fn find_user(id: u64) -> Option[User] {
    // returns Some(user) or None
}

fn parse_number(s: String) -> Result[i64, ParseError] {
    // returns Ok(number) or Err(error)
}

We'll cover error handling patterns in depth in Chapter 7.

Shared types

By default, structs and enums have value semantics — assigning or passing them moves or copies the data. For types that need reference semantics (shared ownership, graph structures), prefix with shared:

shared struct Node {
    value: i64,
    children: Vec[Node],
}

A shared struct is automatically reference-counted. Multiple owners can point to the same data without explicit Rc or Arc wrappers. The compiler picks the right reference-counting strategy behind the scenes.

Use shared when your data naturally has multiple owners. Use regular structs (the default) for everything else.