10.xChapter 10 summary and quiz

Last updated June 13, 2026

This chapter handed you the first half of the type-building toolkit. Quick review, then a quiz that builds a small type from scratch.

Quick review

A struct is a program-defined type that groups related values under one name (10.1). Types are named in UpperCamelCase, fields in snake_case, and the lints enforce it. A struct answers "what parts make up this thing?"; chapter 11's enums answer "which of these things is it?"

You define a struct with named fields and create an instance by giving every field a value; leaving one out is E0063, because a half-built value is uninitialized (10.2). Read and write fields with dot notation. Mutability attaches to the binding, not the field: there's no per-field mut, so the whole instance is mutable or none of it is. Two pieces of syntax cut the repetition (10.3): field init shorthand writes name for name: name when a variable matches, and struct update syntax (..base) fills remaining fields from another instance, moving (or copying) them per chapter 8's rules.

Beyond named-field structs there are tuple structs (struct Rgb(u8, u8, u8)), whose fields are positional, and unit structs (struct Marker;), which hold no data (10.4). A single-field tuple struct is the newtype pattern: wrapping a value to give it a distinct type, turning "this f64 is meters" from a comment into a compiler check.

A method is a function defined in an impl block that takes self and is called with a dot (10.5). The receiver is the chapter 9 parameter decision once more: &self to read, &mut self to modify in place, self to consume. An associated function has no self and is called on the type with :: (10.6). Rust has no constructors; ::new is just an associated function named by convention that returns a fresh instance, and #[derive(Default)] supplies a ::default() that zeroes every field.

A struct can't be printed with {} until you implement Display yourself (chapter 16), but #[derive(Debug)] enables {:?} and the pretty {:#?} for free (10.7). derive is the compiler writing obvious boilerplate; each name in the list is a trait, chapter 16's subject. Finally, a struct owns its fields (10.8): moving the struct moves them all, dropping it drops them all, and it's Copy only if every field is. Storing a reference in a struct needs a lifetime (E0106), which is why your structs hold owned fields like String until chapter 17. The rectangle project (10.9) walked the whole thing from loose variables to a type with methods.

Quiz time

Question #1

From memory: what's the difference between a method and an associated function, and how is each called?

Show solution

A method takes self (as &self, &mut self, or self) and is called on an instance with a dot: r.area(). An associated function takes no self and is called on the type with a path: Rectangle::new(...). Both live in impl blocks and belong to the type.

Question #2

Compiles or not? For each failure, name the cause.

a)

struct Point { x: f64, y: f64 }
fn main() {
    let p = Point { x: 1.0 };
    println!("{}", p.x);
}

b)

#[derive(Debug)]
struct P { x: f64 }
fn main() {
    let p = P { x: 1.0 };
    println!("{:?}", p);
}

c)

struct P { x: f64 }
fn main() {
    let p = P { x: 1.0 };
    p.x = 2.0;
    println!("{}", p.x);
}

d)

#[derive(Debug)]
struct Note { body: String }
fn main() {
    let a = Note { body: String::from("hi") };
    let b = a;
    println!("{:?} {:?}", a, b);
}
Show solution

a) Refused, E0063: missing field y. Every field must be initialized. b) Compiles, prints P { x: 1.0 }. c) Refused, E0594: p isn't mut, so its field can't be assigned. d) Refused, E0382: let b = a; moves a (a Note holds a String, so it isn't Copy), and a is then read after the move. Fix with &a or a.clone() (after deriving Clone).

Question #3

Choose the receiver (&self, &mut self, self) or "associated function (no self)" for each:

a) Circle::unit() returning a circle of radius 1 b) a method returning the circle's circumference c) a method that grows the radius by a given amount d) a method that consumes the circle and returns its area, discarding the circle

Show solution

a) Associated function, no self: it builds a new instance. b) &self: reads only. c) &mut self: changes a field in place. d) self: consumes the instance (the circle is thrown away).

Question #4

The capstone. Define a BankAccount struct with an owner: String and a balance: u64 (cents, the lesson 6.x "count money in integer cents" rule). Give it: an associated function new(owner: String) -> BankAccount starting at zero balance; a deposit(&mut self, cents: u64) method; a withdraw(&mut self, cents: u64) method that only subtracts if the balance is sufficient (otherwise leaves it unchanged); and a summary(&self) -> String returning something like "Ada: 1250 cents". Derive Debug. In main, open an account, deposit 2000, withdraw 750, attempt to withdraw 9999 (should be refused), and print the summary.

Show solution
#[derive(Debug)]
struct BankAccount {
    owner: String,
    balance: u64,
}

impl BankAccount {
    fn new(owner: String) -> BankAccount {
        BankAccount { owner, balance: 0 }
    }

    fn deposit(&mut self, cents: u64) {
        self.balance += cents;
    }

    fn withdraw(&mut self, cents: u64) {
        if cents <= self.balance {
            self.balance -= cents;
        }
    }

    fn summary(&self) -> String {
        format!("{}: {} cents", self.owner, self.balance)
    }
}

fn main() {
    let mut account = BankAccount::new(String::from("Ada"));
    account.deposit(2000);
    account.withdraw(750);
    account.withdraw(9999);   // refused: balance unchanged
    println!("{}", account.summary());
}
Ada: 1250 cents

Design notes. new is an associated function (no self) using field init shorthand for owner. deposit and withdraw take &mut self because they change the balance, so account must be a mut binding. withdraw guards with if cents <= self.balance, so the 9999 attempt does nothing rather than underflowing a u64 (which would panic in debug, lesson 4.4). summary takes &self and uses format! (lesson 5.5) to build an owned String. The whole type owns its data, so it could move into a function or a collection without dragging a lifetime along.

If you reached for f64 to hold dollars, reread lesson 6.x: money in floats accumulates rounding error (lesson 4.5). Integer cents is the rule.

You can now invent types whose parts coexist. Chapter 11 gives you the other kind: enums, where a value is exactly one of several alternatives, where Option retires the null-pointer mistake, and where match becomes the most powerful tool in the language.