Background
In Rust, types often encapsulate other values (e.g., Box, Rc), and developers wanted transparent access to nested values, similar to pointers in C++. The trait Deref (and DerefMut) was introduced along with special auto-deref logic for convenient access through methods and operators.
The Issue
Confusion about when and how pointers and smart pointers are automatically dereferenced leads to compilation errors, unexpected behavior of methods, and sometimes even suboptimal code due to unnecessary copies or borrows.
The Solution
The Deref trait allows you to define your own dereferencing rule (e.g., Box<T> behaves like T due to implementing Deref). The auto-deref system attempts to "dereference" references to Deref types when calling methods and operators. This allows you to write:
use std::rc::Rc; fn print_len(s: &str) { println!("{}", s.len()); } let s: Rc<String> = Rc::new("hello".into()); print_len(&s); // auto-deref: &Rc<String> -> &String -> &str
Key Points:
*, as well as when matching reference types.Does auto-deref happen every time a developer "expects" it?
No: deref coercion occurs only when calling methods or when a reference to another type, specified in the signature through Deref Target, is required. It does not work in all expressions, such as in pattern matching.
Can deref coercion break borrowing and ownership rules?
No. Deref coercion fully respects the borrowing rules — it is not possible to obtain two mutable references through Deref, violating Rust's ownership safety.
Is auto-deref possible from &T to &U, if T: Deref<Target=U> and both types are not explicitly related?
Yes, when calling a function expecting &U, passing &T where T implements Deref<Target=U> will lead to automatic conversion.
Example code:
struct Wrapper(String); impl std::ops::Deref for Wrapper { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } fn takes_str(s: &str) {} let w = Wrapper("mytext".into()); takes_str(&w); // auto-deref: &Wrapper -> &String -> &str
A developer implemented their own smart pointer but did not add a Deref implementation, expecting their type to behave like a normal value.
Pros:
The type compiles, and it is possible to explicitly access the nested value.
Cons:
Auto-deref stops working when calling methods, causing inconvenience when using standard library functions and third-party libraries.
A custom wrapper structure implements Deref and DerefMut similarly to Box, allowing seamless integration with all standard functions.
Pros:
Convenience, transparent operation of most language functions, neatness, and smooth integration with the rest of the code.
Cons:
Risk of complicating the Deref interface if the target type is not obvious.