Auto-dereferencing in Rust: rules, examples, and "magic" under the hood

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Auto-dereferencing in Rust: how it works and why it's not magic

\n\n

If you've ever written in Rust, you've probably noticed that the compiler allows you to call methods on references and smart pointers as if you were working with the value itself. This is not magic and not a bug — it's a strictly defined mechanism called auto-dereferencing.

\n\n

On Stack Overflow, the question «What are Rust's exact auto-dereferencing rules?» has gathered thousands of views — and for good reason. Understanding these rules turns Rust code from a «set of strange errors» into an elegant and predictable tool. Let's look under the hood.

\n\n

What is auto-dereferencing and why is it needed?

\n\n

In Rust, we have references (&T, &mut T) and smart pointers (Box<T>, Rc<T>, Arc<T>). When you write my_box.some_method(), the compiler automatically applies dereferencing to reach the inner value T. Without this mechanism, you would have to write (*my_box).some_method() or (**my_ref).some_method() — which quickly becomes tedious and clutters the code.

\n\n

Auto-dereferencing makes code clean, but it's important to understand its boundaries and exact rules to avoid pitfalls.

\n\n

Exact rules of auto-dereferencing in Rust

\n\n

Let's break down the official specification. When the compiler encounters an expression like x.method(), it tries to find a method named method for type T, where T is the type of x. If the method is not found, the compiler begins the process of automatic dereferencing (or taking a reference).

\n\n

Here is the sequence of steps the compiler performs (simplified):

\n\n
    \n
  1. Step 1: Try to find a method for type T (as is).
  2. \n
  3. Step 2: If not found — try to find a method for &T or &mut T (taking a reference).
  4. \n
  5. Step 3: If not found — apply Deref, i.e., dereference once (get U from T).
  6. \n
  7. Step 4: Repeat steps 1-3 for the new type U.
  8. \n
  9. Step 5: If after several dereferences the method is still not found — compilation error.
  10. \n
\n\n

Important: the compiler does not create an infinite chain. It applies dereferencing until it finds a method or hits a type that does not implement Deref.

\n\n

Examples: from simple to complex

\n\n

Let's look at code that demonstrates these rules in action.

\n\n

Example 1: Basic reference

\n\n
struct Point {\n    x: i32,\n    y: i32,\n}\n\nimpl Point {\n    fn distance_from_origin(&self) -> f64 {\n        ((self.x.pow(2) + self.y.pow(2)) as f64).sqrt()\n    }\n}\n\nfn main() {\n    let p = Point { x: 3, y: 4 };\n    let r = &p; // &Point\n    println!("{}", r.distance_from_origin()); // 5.0\n}
\n\n

Here r is &Point. The method distance_from_origin takes &self. The compiler sees: r is &Point. It tries to find a method for &Point — it finds it! Everything works without explicit dereferencing.

\n\n

Example 2: Double reference

\n\n
fn main() {\n    let p = Point { x: 3, y: 4 };\n    let r1 = &p;     // &Point\n    let r2 = &r1;    // &&Point\n    println!("{}", r2.distance_from_origin()); // 5.0\n}
\n\n

The type of r2 is &&Point. The compiler tries to find a method for &&Point — no. It tries to take a reference: &&&Point — also no. Then it applies Deref once: it gets &Point. For

Blogs

Book Recommendations