You can call len() on an Rc<Vec<T>> or trim() on an Rc<String> even
though those methods belong to the contained values. Rust follows dereference
implementations when looking for methods and when converting certain borrowed
arguments. This article separates those conveniences from ownership transfer.
Follow a borrowed view
Deref has an associated Target type and a method with the signature
fn deref(&self) -> &Self::Target. The target may be unsized, such as str
or [T]. The returned reference borrows from self; it does not transfer
ownership of the target.
This complete example uses several standard implementations:
use std::cell::RefCell;
use std::rc::Rc;
fn trimmed_len(text: &str) -> usize {
text.trim().len()
}
fn main() {
let text = Rc::new(String::from(" hello "));
assert_eq!(trimmed_len(&text), 5);
assert_eq!(text.trim(), "hello");
let words = Rc::new(vec![String::from("a"), String::from("bb")]);
assert_eq!(words.len(), 2);
assert_eq!(words.iter().map(String::len).sum::<usize>(), 3);
let boxed: Box<[u8]> = vec![1, 2, 3].into_boxed_slice();
assert_eq!(boxed.len(), 3);
let cell = RefCell::new(vec![4, 5]);
let guard = cell.borrow();
assert_eq!(guard.first(), Some(&4));
}
At the trimmed_len call, the expected &str allows a chain of deref
coercions: &Rc<String> to &String, then to &str. The data is borrowed,
not cloned. Coercions occur at designated sites, such as function arguments
and bindings with an explicit expected type; Rust does not apply this chain
indiscriminately to every expression.
Method calls have their own lookup process involving automatic dereferencing and borrowing of the receiver. It considers candidate receiver types and their shared and mutable references, with unsizing also possible. Lookup can stop at a method on the wrapper rather than reaching the innermost value. Use an explicit type or qualified call when a name collision is confusing. The method-call reference describes the candidate order in detail.
Mutable access needs DerefMut
DerefMut extends Deref with
fn deref_mut(&mut self) -> &mut Self::Target. It supports mutable access
through the wrapper. Box<T> and RefMut<'_, T> implement it; Rc<T> does
not.
Check your understanding Why doesn't a mutable Rc handle give mutable access to its value? Show answer Hide answer
Other Rc handles may share the allocation. Exclusive access to one handle
does not establish exclusive access to the contained value. Unconditionally
returning &mut T while aliases remain would violate Rust's borrowing rules.
For a uniquely held Rc, Rc::get_mut can return mutable access after checking
that no other Rc or Weak pointers share the allocation. For cloneable data,
Rc::make_mut can instead provide copy-on-write behavior. Those explicit APIs
handle cases that an unconditional dereference cannot assume away.
A RefCell checks borrowing before it creates a guard. A mutable guard then
provides mutable dereferencing for the duration of that borrow:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let cell = RefCell::new(vec![1]);
{
let mut guard = cell.borrow_mut();
guard.push(2);
assert!(cell.try_borrow().is_err());
}
assert_eq!(&*cell.borrow(), &[1, 2]);
let mut unique = Rc::new(vec![3]);
Rc::get_mut(&mut unique).unwrap().push(4);
let shared = Rc::clone(&unique);
assert!(Rc::get_mut(&mut unique).is_none());
Rc::make_mut(&mut unique).push(5);
assert_eq!(&*shared, &[3, 4]);
assert_eq!(&*unique, &[3, 4, 5]);
}
borrow_mut() may panic if another borrow conflicts; try_borrow_mut()
reports failure instead. Once the guard exists, dereferencing it is not a
second request to borrow the cell.
Dereferencing is not generally a move
The * operator identifies the target place. What happens next depends on
the context and the target type: it may be borrowed, copied or, where Rust
permits it, moved. Reading an i32 through an Rc copies the number. Trying
to move a String out through an Rc fails with E0507.
An owned Box<T> has special language support for moving out its contents.
That behavior does not follow merely from implementing Deref yourself:
use std::rc::Rc;
fn main() {
let number = Rc::new(7_i32);
let copied = *number;
assert_eq!(copied, 7);
assert_eq!(*number, 7);
let boxed = Box::new(String::from("owned"));
let moved: String = *boxed;
assert_eq!(moved, "owned");
let text = Rc::new(String::from("shared"));
let cloned: String = (*text).clone();
assert_eq!(cloned, "shared");
let recovered = Rc::try_unwrap(text).unwrap();
assert_eq!(recovered, "shared");
}
Cloning the inner String creates an independent value. Cloning the Rc creates
another handle to the same value. Rc::try_unwrap can recover ownership when
no other strong Rc handles remain; it is a separate ownership API, not a
coercion.
Trait bounds do not automatically follow the target
A generic T: SomeTrait requirement is checked for the actual T. An
Rc<Concrete> does not acquire every trait that Concrete implements just
because methods can be found through dereferencing.
This example explicitly borrows the contained String so inference selects
T = String:
use std::rc::Rc;
trait Label {
fn label(&self) -> &str;
}
impl Label for String {
fn label(&self) -> &str { self.as_str() }
}
fn show<T: Label>(value: &T) -> &str {
value.label()
}
fn main() {
let text = Rc::new(String::from("hello"));
assert_eq!(show(&*text), "hello");
}
Changing the call to show(&text) infers T = Rc<String> and fails because
that type has no Label implementation. Explicitly specifying
show::<String>(&text) provides an expected reference type and allows
coercion. A by-value generic API needs a different solution: borrowing the
target will not satisfy a trait bound that is implemented only for its owned
type.
Treat Deref as part of your public API
Smart pointers are a common use, but String to str and Vec<T> to [T]
are useful examples too. The question is whether implicit access to the target
matches the abstraction. Keep dereferencing cheap and unsurprising; the trait
itself does not enforce a cost bound or prevent a custom implementation from
panicking. See the standard-library guidance.
For an inventory with validation rules, exposing selected methods may be
clearer than dereferencing to its internal vector. Deref alone permits
shared access such as len() and iter(); it does not permit clear(),
sort() or drain() through a mutable vector reference. Adding DerefMut
would make those operations available and could bypass your intended update
methods. Shared methods can matter too if the target uses interior mutability.
Target methods can collide with wrapper methods as either API evolves. Removing a public dereference implementation can break existing callers. These are design tradeoffs, not something a lint can decide for you.
An explicit accessor or AsRef can offer a borrowed view without implicit
method lookup. Borrow has an additional contract: equality, ordering and
hashing must agree between the owned and borrowed forms where those traits
apply. It is not interchangeable with AsRef for an arbitrary field view.
See the Borrow documentation.
Check your understanding When is &[T] a better parameter than &Vec<T>? Show answer Hide answer
Use a slice when the function needs only the elements. It accepts vector
borrows, arrays and subslices. A vector reference additionally exposes
vector-specific information such as capacity, so changing it is appropriate
only when the function does not need that information. The same reasoning
often favors &str over &String and &T over &Box<T>.
Clippy's ptr_arg and borrowed_box can suggest narrower input requirements;
explicit_auto_deref can identify unnecessary explicit dereferencing. Read
each suggestion in context. The goal is an API that asks for the capabilities
it uses, with ownership and mutation rules that callers can predict.