Skip to content
AgileLogics
6 min read

Structs

Prerequisites: Vectors

A struct groups related values under one name, with a named field for each value. It's how you create your own data types.

Syntax

struct User {
    name: String,
    email: String,
    age: u32,
    active: bool,
}

let user = User {
    name: String::from("Ada"),
    email: String::from("ada@example.com"),
    age: 36,
    active: true,
};
println!("{}", user.name);
  • Struct names use PascalCase; field names use snake_case.
  • Create an instance by giving a value for every field, in any order. Read fields with dot notation.
  • To change a field, the whole instance must be mut — Rust doesn't allow marking individual fields as mutable.
  • Field init shorthand — if a variable has the same name as a field, write User { name, age, ... } instead of name: name.
  • Struct update syntax — User { age: 40, ..other } copies the remaining fields from other.
  • Tuple structs have fields without names: struct Point(i32, i32);. Access them with .0, .1. They're useful for giving a tuple a distinct type — a Meters(f64) can't be mixed up with a Seconds(f64).
  • #[derive(Debug)] above a struct lets you print it with {:?} or {:#?}.

Example

#[derive(Debug)]
struct User {
    name: String,
    age: u32,
    active: bool,
}

#[derive(Debug)]
struct Point(i32, i32);

fn build_user(name: String, age: u32) -> User {
    User {
        name, // field init shorthand
        age,
        active: true,
    }
}

fn main() {
    let mut user = build_user(String::from("Ada"), 36);
    println!("{} is {}, active: {}", user.name, user.age, user.active);

    user.age += 1;
    println!("After birthday: {}", user.age);

    let other = User {
        name: String::from("Grace"),
        ..user // take the remaining fields from `user`
    };
    println!("{other:?}");

    let origin = Point(0, 0);
    let p = Point(3, -4);
    println!("{origin:?} and ({}, {})", p.0, p.1);

    println!("{user:#?}");
}

Expected output

Ada is 36, active: true
After birthday: 37
User { name: "Grace", age: 37, active: true }
Point(0, 0) and (3, -4)
User {
    name: "Ada",
    age: 37,
    active: true,
}

user is still usable after the update syntax because only age and active were taken from it, and both are Copy types. If ..user had supplied the name field, the String would have moved out of user.

Common mistakes

  • Printing a struct without #[derive(Debug)]:
error[E0277]: `User` doesn't implement `Debug`

The compiler suggests adding #[derive(Debug)] above the struct.

  • Changing a field of an immutable instance. user.age += 1; requires let mut user.
  • Leaving out a field. Every field needs a value. The compiler reports error[E0063] and names the missing field.
  • Using &str for struct fields as a beginner. A struct that holds references needs lifetimes, an intermediate topic. Use String so the struct owns its data.

Practice

Define a struct Book with title: String, author: String, and pages: u32, deriving Debug. Create one, print a sentence using its fields, then print the whole struct with {:?}:

The Rust Book by Steve Klabnik, 560 pages
Book { title: "The Rust Book", author: "Steve Klabnik", pages: 560 }
Show solution
#[derive(Debug)]
struct Book {
    title: String,
    author: String,
    pages: u32,
}

fn main() {
    let book = Book {
        title: String::from("The Rust Book"),
        author: String::from("Steve Klabnik"),
        pages: 560,
    };
    println!("{} by {}, {} pages", book.title, book.author, book.pages);
    println!("{book:?}");
}