R
RUSTY YELLOW PAGES

selfKeyword


Explanation

self (lowercase) has two unrelated meanings, separated entirely by position — the same by-position-disambiguation as &/*.

1. The method receiver (inside a trait/impl method signature). self as the first parameter of a method means "the instance the method was called on," and the exact form chosen tells the caller what happens to their value:

  • self (by value) — the method takes ownership of the receiver, consuming it. Once called, the original binding is gone, same as passing any owned value into a function. Common for builder-style chained methods and conversions that transform a value into something else.
  • &self — the method borrows the receiver immutably. The most common receiver form: read-only inspection, caller keeps the value afterward.
  • &mut self — the method borrows the receiver mutably, able to change it in place, without taking ownership.

self with no type annotation is shorthand for self: Self (or self: &Self / self: &mut Self) — see Self for the capital-S placeholder this refers to. A method with none of these as its first parameter is an associated function, not a method (Client::new(...), not client.new(...)) — it's called on the type itself, with no receiver at all.

2. self in a path or use statement — an entirely different sense, referring to the current module itself, not an instance of anything. Inside use std::io::{self, Read};, the self imports the io module name itself (so io::Error is reachable), while Read alongside it imports that one item directly — one use line covering both the module and specific items from it, instead of two separate use statements. self also appears in relative paths outside use (self::helper() from within a module, meaning "look in this same module"), though use {self, ...} grouped-import is by far the more common sighting.

Usage examples

Reading a value through `&self`

struct Order { total_cents: u64 }

impl Order {
    fn total(&self) -> u64 { // <- `&self`: reads the receiver, caller keeps ownership
        self.total_cents
    }
}

Implementing traits

A builder type's chained methods take self by value, consuming and returning the modified builder each step, while a plain inspection method takes &self — the receiver form is chosen per method, based on what that method actually does.

struct RequestBuilder {
    url: String,
    retries: u32,
}

impl RequestBuilder {
    fn retries(mut self, n: u32) -> Self { // <- `self` by value: consumes and returns the builder
        self.retries = n;
        self
    }

    fn url(&self) -> &str { // <- `&self`: read-only, caller keeps using the builder afterward
        &self.url
    }
}

let builder = RequestBuilder { url: "https://api.example.com".into(), retries: 0 };
let builder = builder.retries(3); // <- consumes the old `builder` binding, returns a new one
println!("{}", builder.url());

An owned self receiver is what makes fluent method chaining possible — each call consumes the previous builder state and hands back a new one — while &self on url signals "just reading, you still own this afterward"; the Rust Book's method-syntax chapter covers exactly this choice among self, &self, and &mut self.

Designing a public API

Importing both a module and specific items from it in one line uses the path-position sense of self — a distinct meaning from the receiver, but still governed by its position (inside a use group, not a method signature).

use std::io::{self, Read, Write}; // <- `self` here imports the `io` module itself, alongside two items from it

fn describe_error(err: &io::Error) -> String { // <- reachable because `self` imported the `io` module name
    format!("io error: {err}")
}

fn copy_all(mut source: impl Read, mut sink: impl Write) -> io::Result<u64> {
    std::io::copy(&mut source, &mut sink)
}

Without the self in the group, io::Error and io::Result would be unreachable unless the module was imported separately (use std::io; on its own line) — grouping self alongside specific items is the idiomatic way to get both without two use statements, as shown throughout the Rust Reference's use-declarations section.

Explanation

Embedded support: Full

Both senses of self mean exactly the same thing under #![no_std]. The method-receiver sense is what nearly every embedded-hal driver method uses to talk to a peripheral — &mut self on a method that needs to change the peripheral's state (write a register, toggle a pin), &self on one that only reads it — with no difference in meaning or runtime cost from the hosted case.

Usage examples

A driver method taking `&mut self` to talk to a peripheral

struct Led<P> { pin: P }

impl<P: embedded_hal::digital::OutputPin> Led<P> {
    fn toggle_on(&mut self) -> Result<(), P::Error> { // <- `&mut self`: mutates the driver's peripheral state
        self.pin.set_high()
    }
}