Methods and impl Blocks

Methods and impl Blocks

Give your structs behavior, not just data: how impl blocks and &self work, why Rust lets a method share a name with a field, and what that mysterious :: in String::from has meant all along.

11 min read4 learning objectives

What You'll Learn

  • Define methods on a struct using an impl block
  • Choose correctly between &self, &mut self, and self — and explain what each promises the caller
  • Write associated functions (constructors) and understand the :: syntax you have been using since lesson one
  • Recognize that a method and a field can share a name without conflict

Right where lesson 12 left off: Rectangle can hold a width and a height, and that's the entirety of what it can do. Let's teach it to actually do something with them.

Giving a struct its own functions

Functions that belong to a specific type are called methods, and you define them inside an impl block — short for "implementation":

Two new pieces of syntax here, and both are worth lingering on. impl Rectangle { ... } says "everything inside here is associated with the Rectangle type." And that first parameter, &self, is what makes this a method rather than a free-floating function — it's how the method refers to whichever specific instance it was called on.

Once you've written that, you call it with familiar dot notation — rect.area(), not area(&rect). Same computation either way, but rect.area() reads like an actual sentence, and that habit compounds beautifully the moment you start chaining several calls together.

Wait — &self? Haven't I seen that shape before?

You have. &self is shorthand for self: &Self, and Self (capital S) is just an alias for "whatever type this impl block belongs to" — Rectangle, in this case. Strip the sugar away, and a method takes a reference to the instance it's called on — the exact same kind of reference, spelled with the exact same &, that you met two lessons ago in references and borrowing. Every single rule you already know about borrowing applies here too. No exceptions, no special cases for methods.

And because references come in flavors, methods do too — which means you get to choose, deliberately, exactly how much access each one needs:

Read each signature as a promise the method is making to its caller, before you've even looked at its body:

  • &self"I'm only going to look." The instance is borrowed immutably; you, and everyone else, can keep using it after the call returns.
  • &mut self"I need to change you." A mutable borrow — which means, exactly like any other &mut reference, the instance itself must be declared mut.
  • self"I'm taking you. You won't get this back." Ownership moves into the method, and — precisely like passing a String into a function back in the ownership lesson — the original binding is consumed. That commented-out final line would be a compile error, and at this point in the course, you can probably explain exactly why without rereading anything.

Most methods you write will take &self. &mut self shows up when a method's entire job is to mutate. Plain self is the rarest of the three — reach for it when a method's job is to transform something into a different value entirely, consuming the original as it goes (think .into_iter(), or the final .build() on a builder).

A method can share a name with a field

This compiles, and it isn't a typo: rect.width (no parentheses) reaches into the field; rect.width() (with parentheses) calls the method. Rust keeps the two perfectly distinct, and the convention this unlocks is genuinely useful — a method that shares a name with a private field, returning some derived check or a read-only view of it, is the idiomatic Rust answer to what other languages call a "getter."

Methods can take more than just self

Extra parameters slide in right after self, exactly like in any other function. can_hold borrows another Rectangle — notice it takes &Rectangle, not Rectangle; there's no reason to demand ownership of something you're merely comparing against. And read that call site again: rect1.can_hold(&rect2) genuinely reads like the English question you're asking.

Associated functions: no self required

Not everything inside an impl block needs an instance to work on. Leave self off the parameter list entirely, and you've written an associated function — most often used for building new instances:

Two things worth noticing here. First — that :: in Rectangle::square(20). You've been typing that exact syntax since lesson one, every single time you wrote String::from(...). Now you finally know precisely what it means: "call the associated function named square that belongs to the type Rectangle." Second — Self (capital S) shows up again, this time as a return type and inside the constructor expression. It's just a convenient stand-in for "the type this impl block is for," which means you could rename Rectangle to something else later without touching a single line inside the block.

One more fact worth filing away: you're allowed to write several separate impl Rectangle { ... } blocks for the same type — the compiler quietly treats them as one combined block. There's no reason to split them up in code this small, but once generics and traits enter the picture, organizing related methods into focused impl blocks becomes a genuinely useful tool, and you'll see it constantly in real crates.

Quick exercise

Add a perimeter method to Rectangle (it should return a u32 and take &self), plus a constructor-style associated function from_square(size: u32) -> Self that does the same job as square but with a more explicit name. Then write a small main that builds a couple of rectangles and prints whether either one can hold the other.

You can now give your types real behavior, not just storage — which covers most of what "object-oriented-feeling" code looks like in Rust, minus inheritance (Rust simply doesn't have it, and you'll see why that's not the loss it might sound like once we reach traits). Next, we look at the other half of modeling data: types that represent one of several possible shapes, instead of always the same one. Meet enums.