Skip to content
Lesson 13 of 15

Step 1 of 7 · Reading · ~3 min

Learn

Structs, Enums, and Error Handling

Structs

A struct bundles several related values into one named type of your own. By the end of this lesson you can define a struct, build instances of it, and write methods that read the fields of the value they are called on and of a second value passed in.

Defining a struct and building an instance

Each value inside a struct is a field, and each field has a name and a type. A value built from the struct is an instance.

struct Span {
    start: i32,
    end: i32,
}

fn main() {
    let morning = Span { start: 9, end: 12 };
    println!("{} to {}", morning.start, morning.end); // 9 to 12
}

You must give every field a value when you build an instance, in any order, and you read a field with a dot: morning.end is 12. Fields of type i32 are Copy, so reading them never moves anything.

Methods live in an impl block

A method is a function attached to a struct. You write it inside impl Span { ... }, and its first parameter is the receiver, the instance the method was called on:

  • &self borrows the instance read-only (the common case),
  • &mut self borrows it so the method can change fields,
  • self takes ownership and consumes it.

Inside the method, self.start is the field of the instance you called it on.

impl Span {
    fn length(&self) -> i32 {
        self.end - self.start
    }

    fn gap_until(&self, later: &Span) -> i32 {
        later.start - self.end
    }
}

let morning = Span { start: 9, end: 12 };
let evening = Span { start: 17, end: 20 };
println!("{}", morning.length());            // 3
println!("{}", morning.gap_until(&evening)); // 5

morning.gap_until(&evening) runs with self = morning and later = evening. Trace it: later.start is 17, self.end is 12, so the result is 5. The second instance is passed as &Span, a borrow, exactly as the ownership lesson borrowed a String; the caller keeps evening.

The trap is reading a field from the wrong instance, or the wrong field. self.start - later.end compiles just as happily and prints -11. The compiler checks types, not meaning: both fields are i32, so any mix-up is legal code with a wrong answer.

Associated functions

A function in an impl block with no receiver is an associated function. You call it on the type with ::, and it is the usual way to write a constructor:

impl Span {
    fn hour_at(start: i32) -> Span {
        Span { start: start, end: start + 1 }
    }
}
let lunch = Span::hour_at(13);   // start 13, end 14

Tuple structs, update syntax, derive

  • A tuple struct has no field names: struct Rgb(u8, u8, u8); builds with Rgb(255, 0, 0) and reads with .0, .1, .2.
  • Struct update syntax copies the remaining fields from another instance: Span { end: 18, ..evening } takes start from evening. Non-Copy fields such as a String are moved out of it.
  • #[derive(Debug)] above the struct lets println!("{:?}", morning) print Span { start: 9, end: 12 }. Without it, {:?} does not compile.

Common mistakes

  • Mixing up x and y. Subtracting a y field from an x field compiles because both are i32. For points (1, 5) and (4, 9) that prints 65 instead of 25.
  • Squaring only one axis. For (3, 7) and (3, 2) it prints -5 instead of 25.
  • Squaring the sum of the differences. For (2, 3) and (5, -1) the differences are 3 and -4; their sum squared is 1, but the correct answer is 25.
  • Taking the square root. The task asks for the squared distance: (0, 0) to (3, 4) is 25, not 5.

Your exercise

Fill in the distance_sq method on Point. The starter already defines the struct, reads the four numbers, builds the two points and prints the method's result; only the method body is yours. Use self for the first point and other for the second, and keep each axis on its own.

Up nextEnums and Pattern MatchingStructs, Enums, and Error Handling

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…