Loading pageā¦
Rust walkthroughs
Loading pageā¦
std::ops::Deref trait and how does it enable smart pointer patterns?std::ops::Deref provides the dereference operator (*) for custom types, enabling smart pointers to behave like regular references by transparently delegating access to their inner values. The trait's signature fn deref(&self) -> &Self::Target returns a reference to the inner value, allowing types like Box<T>, Rc<T>, and Arc<T> to work seamlessly with code expecting &T. Beyond explicit dereferencing, Rust's deref coercion automatically converts references to smart pointers into references to their contents, so &String can be passed where &str is expected, and &Vec<T> where &[T] is expected. This mechanism enables smart pointer types to integrate naturally into the language's reference semantics.
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let boxed = MyBox(42);
// Explicit dereference
let value = *boxed;
println!("{}", value); // 42
// Deref returns reference
let reference: &i32 = &*boxed;
println!("{}", reference); // 42
}Deref::deref returns a reference to the inner value, enabling the * operator.
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}The Target associated type defines what type the pointer points to.
use std::ops::Deref;
struct SmartPtr<T> {
value: T,
}
impl<T> Deref for SmartPtr<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
fn main() {
let ptr = SmartPtr { value: String::from("hello") };
// Access inner value
println!("{}", *ptr); // "hello"
// Access methods on inner value
println!("{}", ptr.len()); // 5
}Deref allows calling methods on T directly through the smart pointer.
use std::ops::Deref;
struct MyString(String);
impl Deref for MyString {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
fn takes_str(s: &str) {
println!("Got: {}", s);
}
fn main() {
let my_string = MyString(String::from("hello"));
// Deref coercion: &MyString -> &String -> &str
takes_str(&my_string);
// Without deref coercion, would need:
takes_str(&*my_string); // explicit
takes_str(&*(&*my_string)); // even more explicit
}Deref coercion chains multiple dereferences automatically.
use std::ops::Deref;
struct Wrapper<T>(T);
impl<T> Deref for Wrapper<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn main() {
let wrapper = Wrapper(vec![1, 2, 3]);
// Method resolution: wrapper doesn't have push, but Vec does
// Rust tries: wrapper.push()
// Then: (*wrapper).push() // via Deref
// Mutable version needs DerefMut
// Read-only methods work directly
println!("{}", wrapper.len()); // 3
println!("{:?}", wrapper.as_slice()); // [1, 2, 3]
}Rust's method resolution automatically dereferences to find methods.
use std::ops::{Deref, DerefMut};
struct MutPtr<T> {
value: T,
}
impl<T> Deref for MutPtr<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T> DerefMut for MutPtr<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.value
}
}
fn main() {
let mut ptr = MutPtr { value: vec![1, 2, 3] };
// Mutable access via DerefMut
ptr.push(4); // Calls Vec::push through DerefMut
println!("{:?}", *ptr); // [1, 2, 3, 4]
}DerefMut enables mutable access and method calls.
use std::ops::Deref;
use std::convert::AsRef;
struct MyVec(Vec<i32>);
impl Deref for MyVec {
type Target = Vec<i32>;
fn deref(&self) -> &Vec<i32> {
&self.0
}
}
impl AsRef<Vec<i32>> for MyVec {
fn as_ref(&self) -> &Vec<i32> {
&self.0
}
}
fn main() {
let my_vec = MyVec(vec![1, 2, 3]);
// Deref: implicit coercion
fn takes_slice(s: &[i32]) {
println!("{:?}", s);
}
takes_slice(&my_vec); // Deref coercion
// AsRef: explicit conversion
fn takes_vec_ref(v: &Vec<i32>) {
println!("{:?}", v);
}
takes_vec_ref(my_vec.as_ref()); // AsRef
// Both work, but Deref is implicit, AsRef is explicit
}Deref is for implicit smart pointer behavior; AsRef is for explicit conversions.
fn main() {
let boxed: Box<i32> = Box::new(42);
// Deref allows using Box<T> like T
let value: i32 = *boxed;
// Method calls work through Deref
let boxed_str: Box<str> = "hello".into();
println!("Length: {}", boxed_str.len()); // Deref to str::len
// Deref coercion
fn takes_str(s: &str) {}
takes_slice(&boxed_str); // &Box<str> -> &str
}Box<T> implements Deref<Target=T> for transparent access.
use std::rc::Rc;
use std::sync::Arc;
fn main() {
// Rc<T> for single-threaded
let rc: Rc<String> = Rc::new(String::from("hello"));
println!("{}", rc.len()); // Deref to String::len
// Arc<T> for multi-threaded
let arc: Arc<String> = Arc::new(String::from("world"));
println!("{}", arc.len()); // Deref to String::len
// Both implement Deref<Target=T>
fn takes_str(s: &str) {}
takes_str(&rc); // &Rc<String> -> &String -> &str
takes_str(&arc); // &Arc<String> -> &String -> &str
}Reference-counted pointers use Deref to provide transparent access.
fn main() {
// String owns heap data, but dereferences to &str
let s = String::from("hello");
let slice: &str = &s; // Deref coercion
// Vec owns heap data, but dereferences to &[T]
let v = vec![1, 2, 3];
let slice: &[i32] = &v; // Deref coercion
// This is why String and Vec work with functions expecting slices
fn takes_str(s: &str) {}
fn takes_slice(s: &[i32]) {}
takes_str(&s);
takes_slice(&v);
}String: Deref<Target=str> and Vec<T>: Deref<Target=[T]> enable slice coercion.
use std::ops::Deref;
struct Config {
host: String,
port: u16,
}
struct ConfigBuilder {
host: String,
port: u16,
}
impl ConfigBuilder {
fn new() -> Self {
ConfigBuilder {
host: "localhost".to_string(),
port: 8080,
}
}
fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
}
impl Deref for ConfigBuilder {
type Target = Config;
fn deref(&self) -> &Self::Target {
// Unsafe in real code, but shows the pattern
// Safe version would store Config inside or use different pattern
unsafe { &*(self as *const ConfigBuilder as *const Config) }
}
}
fn main() {
let builder = ConfigBuilder::new().port(3000);
// Access Config fields through builder
println!("Host: {}", builder.host);
}Deref can expose a different view of the same data.
use std::ops::Deref;
struct A<T>(T);
struct B<T>(T);
impl<T> Deref for A<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
impl<T> Deref for B<T> {
type Target = A<T>;
fn deref(&self) -> &A<T> { /* ... */ }
}
fn main() {
// Deref coercion rules:
// 1. From &T to &U when T: Deref<Target=U>
// 2. From &mut T to &mut U when T: DerefMut<Target=U>
// 3. From &mut T to &U when T: Deref<Target=U>
// Rust inserts as many deref coercions as needed
fn takes_i32(_: &i32) {}
let b = B(A(42));
// takes_i32(&b) would work via: &B -> &A -> &i32
}Rust automatically chains deref coercions when needed.
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
fn main() {
let boxed = MyBox(42);
// These work:
let _: &i32 = &*boxed; // Explicit dereference
let _: &i32 = &boxed; // Deref coercion
// This does NOT work:
// let _: i32 = boxed; // Can't move out of dereference
// Deref gives a reference, not ownership
// To move out, need to implement Into or similar
}Deref provides borrowed access; it doesn't enable moves.
use std::ops::Deref;
struct Counter {
count: usize,
}
impl Deref for Counter {
type Target = usize;
fn deref(&self) -> &usize {
&self.count
}
}
fn main() {
let counter = Counter { count: 5 };
// Arithmetic works through deref
let doubled = *counter * 2; // 10
// Comparison works
assert!(*counter == 5);
// But counter is not a usize itself
// let x: usize = counter; // Won't compile
}Deref enables value-based operations through dereferencing.
use std::ops::Deref;
use std::cell::RefCell;
struct Container<T> {
cell: RefCell<T>,
}
impl<T> Deref for Container<T> {
type Target = RefCell<T>;
fn deref(&self) -> &RefCell<T> {
&self.cell
}
}
fn main() {
let container = Container {
cell: RefCell::new(42),
};
// Access RefCell methods directly
*container.borrow_mut() = 100;
// Deref chain: Container -> RefCell -> interior
println!("{}", *container.borrow()); // 100
}Smart pointers often nest, with Deref chains providing access.
use std::ops::Deref;
// DON'T: Deref for type conversion
struct Meters(u32);
struct Kilometers(u32);
// This would be confusing:
// impl Deref for Meters {
// type Target = Kilometers;
// ...
// }
// Meters are not "pointing to" Kilometers
// DO: Use From/Into for type conversion
impl From<Meters> for Kilometers {
fn from(m: Meters) -> Kilometers {
Kilometers(m.0 / 1000)
}
}
// Deref is for "smart pointer" semantics, not type conversionDeref is for smart pointer patterns, not general type conversion.
use std::ops::Deref;
// Deref implementations must be stable:
// - Multiple calls must return the same address
// - The reference must remain valid
struct BadDeref<T> {
values: Vec<T>,
}
// DON'T: Return reference to moved value
// impl<T> Deref for BadDeref<T> {
// type Target = T;
// fn deref(&self) -> &T {
// &self.values.pop().unwrap() // WRONG!
// }
// }
// DO: Return stable reference
impl<T> Deref for BadDeref<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.values
}
}Deref implementations must return stable, valid references.
use std::ops::Deref;
struct StringWrapper<'a> {
inner: &'a str,
}
impl<'a> Deref for StringWrapper<'a> {
type Target = str;
fn deref(&self) -> &str {
self.inner
}
}
fn main() {
let s = String::from("hello");
let wrapper = StringWrapper { inner: &s };
fn takes_str(s: &str) {}
takes_str(&wrapper); // Works with lifetimes
}Deref works with lifetime parameters naturally.
use std::ops::Deref;
use std::sync::{Arc, Mutex, MutexGuard};
struct SharedData<T> {
inner: Arc<Mutex<T>>,
}
impl<T> SharedData<T> {
fn lock(&self) -> SharedGuard<'_, T> {
SharedGuard {
guard: self.inner.lock().unwrap(),
}
}
}
struct SharedGuard<'a, T> {
guard: MutexGuard<'a, T>,
}
impl<T> Deref for SharedGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&*self.guard
}
}
fn main() {
let data = SharedData {
inner: Arc::new(Mutex::new(vec![1, 2, 3])),
};
let guard = data.lock();
// Access inner value through guard
println!("First: {}", guard[0]); // Via Deref
}Guard types use Deref to provide transparent access to protected data.
std::ops::Deref serves one primary purpose: enabling smart pointer types to behave transparently like the values they contain. This is achieved through two mechanisms:
Explicit dereferencing: The * operator calls deref() and returns the inner value. For a Box<T>, *box yields the T inside.
Deref coercion: Rust automatically inserts dereference operations when converting references. If T: Deref<Target=U>, then &T can be passed where &U is expected. This chains: if U: Deref<Target=V>, then &T can pass where &V is expected.
Method resolution: When calling obj.method(), Rust tries obj.method(), then (*obj).method(), then (**obj).method(), and so on. This allows calling methods on T directly through a smart pointer.
Key insight: Deref is not for type conversionāit's for indirection. When a type logically "contains" another type and should behave like that inner type, implement Deref. When two types are related but one doesn't contain the other, use From/Into/AsRef instead.
The smart pointer pattern in Rust relies entirely on Deref. Box, Rc, Arc, String, Vec, RefCell, and many other types implement Deref to provide seamless integration with code expecting references to their contents. Without Deref, every smart pointer would require explicit unwrapping to access the inner value, making generic code that works with both owned and borrowed data much more complex.