🎓 A1Learner
🔍

Rust Structs

A struct is a group of values under names, with a name of its own. Names remove the mix-ups of position.

A tuple cannot name its parts

A rectangle as a plain tuple: the numbers work, but nothing says which is width and which is height:

fn main() {
    let rect = (30, 50);
    let area = rect.0 * rect.1;
    println!("area = {}", area);
    // is .0 the width or the height? the tuple cannot say
}

Define a struct and make one

Name the fields and their types once. Then make a value by naming every field. Read a field back with a dot. The #[derive(Debug)] line lets {:?} print the whole value:

#[derive(Debug)]
struct Rectangle { width: u32, height: u32 }
let rect = Rectangle { width: 30, height: 50 };
println!("width = {} rect = {:?}", rect.width, rect);

Any order — and changeable with mut

The names, not the positions, say what is what, so fields may come in any order. With mut on the whole value, a field can change:

let mut rect = Rectangle { height: 50, width: 30 };
println!("width = {} height = {}", rect.width, rect.height);
rect.width = 10;
println!("now width = {}", rect.width);

Same name? Write it once

When a variable already has a field's name, write just the name. Creating a struct is an expression, so make_user ends with the value itself:

struct User { name: String, age: u32 }
fn make_user(name: String, age: u32) -> User { User { name, age } }
let user = make_user(String::from("Ada"), 36);
println!("{} is {}", user.name, user.age);

Build a new value from an old one

..ada fills the fields left out from ada, and it moves like an assignment. The String name moves out of ada, so ada no longer works as a whole:

let ada = User { name: String::from("Ada"), age: 36 };
let bo = User { age: 41, ..ada };
println!("bo = {} ({}), ada age = {}", bo.name, bo.age, ada.age);

Copy the fields into names

Field reads can fill plain names, one field at a time:

struct Point { x: i32, y: i32 }
let point = Point { x: 3, y: 7 };
let left = point.x;
let y = point.y;
println!("left = {} y = {}", left, y);

Tuple structs and unit structs

A tuple struct has types but no field names, so it is read by position or split in one let. A unit struct like Marker has no fields at all: the name alone is the information.

struct Colour(u8, u8, u8);
struct Marker;
let red = Colour(255, 0, 0);
println!("channels = {} {} {}", red.0, red.1, red.2);
let Colour(r, g, b) = red;
println!("split = {} {} {}", r, g, b);

Example

#[derive(Debug)]
struct Rectangle { width: u32, height: u32 }
fn area(r: &Rectangle) -> u32 { r.width * r.height }
fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("{:?} area = {}", rect, area(&rect));
}

Key points

  • A struct is one named group: the fields carry the values, and the struct name carries the meaning.
  • A struct usually owns its fields.
  • That is why a User stores String, not &str.
  • The text then lives as long as the struct does.
  • Storing a reference is allowed.
  • A stored reference needs lifetimes, from Rust Lifetimes Basics.
  • Rust does not let single fields be marked as mut.
  • A struct expression needs no return word and no let line first.
  • A Copy field of a partly moved struct, like age, still reads fine after the move.
  • A unit struct helps when a kind alone matters, often with generics.

In this example, Rectangle is a struct with two fields, width and height. main creates one with width 30 and height 50. The function area borrows the rectangle with &Rectangle, reads both fields, and multiplies them: 1500. The #[derive(Debug)] line above the struct lets {:?} print the whole value, so you can see its shape and fields together in the output.

Note

Rust has no classes.

Structs hold the data, and the Methods lesson, a little later, adds the behaviour with impl.

#[derive(...)] is called an attribute — an instruction written above an item.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does #[derive(Debug)] give a struct?