Deref / DerefMut Traits
Deref / DerefMut Traits
Level 14 — Advanced Traits & Type System Standard library traits (
std::ops::Derefandstd::ops::DerefMut) that customize the behavior of the dereference operator (*v) and enable automatic Deref Coercion for smart pointers and container types.
1. Prerequisites
- Smart Pointers (
Box,Rc,Arc) — Smart pointer wrapper types that useDerefto expose inner values. - References and Borrowing (
&,&mut) — Borrowing mechanics (&Tand&mut T).
2. Term Category
Rust Standard Traits (smart pointer dereferencing traits): Deref (std::ops::Deref) and DerefMut (std::ops::DerefMut) are operator traits in Rust. Implementing Deref allows a custom type T to define how it converts to a reference of an inner associated type Target (fn deref(&self) -> &Self::Target). This customizes the * dereference operator and unlocks Deref Coercion — allowing references &T to automatically coercion-borrow as &Target when passed to function parameters.
3. Explanation
(1) Design Motivation — "Why did we design this?"
Without Deref and Deref Coercion:
- Every time you wanted to call a string slice (
str) method like.len(),.to_uppercase(), or.contains()on aString, you would have to write(*my_string).as_str().len(). - Every time you wanted to pass a
Vec<u8>to a function expecting a slice&[u8], you would have to writefunction_expecting_slice(my_vec.as_slice()). - Every time you accessed an inner value inside a
Box<User>, you would have to callmy_box.get_inner().name.
Rust introduced Deref / DerefMut to make smart pointers and container wrappers behave as transparently as regular references:
- Explicit Dereferencing:
*my_smart_ptrevaluates to*my_smart_ptr.deref(). - Method Call Deref: Invoking
my_smart_ptr.method()automatically searches formethod()onTargetifSmartPtrdoes not define a method with that name. - Implicit Deref Coercion: Passing
&Stringto a parameter expecting&strautomatically converts&String&strat compile time with zero runtime cost.
(2) Reality Metaphor
Imagine a Clear Protective Smartphone Case:
- A Standard Struct without
Derefis like putting your phone inside a sealed metal safe: every time you want to tap the screen (call a method on the inner value), you must unlock the safe, take the phone out, tap the screen, and put it back in. - Implementing
Derefis like encasing the phone in a clear, ultra-thin protective touch-case:- You are touching the case (holding the
Box<T>orStringwrapper). - When you tap the glass screen (call
.len()or*ptr), your touch passes directly through the protective case to the phone's actual screen (innerTargettype) without any friction or delay.
- You are touching the case (holding the
(3) Code Examples
Short Snippet (Custom Smart Pointer with Deref)
use std::ops::Deref;
/// A custom smart pointer wrapper
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> Self {
MyBox(x)
}
}
// 1. Implement `Deref` to specify associated `Target` type
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let x = 5;
let b = MyBox::new(x);
// Explicit dereferencing using `*` operator (equivalent to `*(b.deref())`):
assert_eq!(5, *b);
println!("Successfully dereferenced MyBox<i32>: {}", *b);
}
Fuller Example (DerefMut & Implicit Deref Coercion)
use std::ops::{Deref, DerefMut};
/// A custom string wrapper demonstrating Deref Coercion to `&str`
pub struct TitleString(String);
impl TitleString {
pub fn new(title: impl Into<String>) -> Self {
TitleString(title.into())
}
}
impl Deref for TitleString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TitleString {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
// Function expecting a plain string slice `&str`
fn print_length(s: &str) {
println!("String length: {} bytes, content: '{}'", s.len(), s);
}
fn main() {
let mut title = TitleString::new("Rust Systems Programming");
// DEREF COERCION: Passing `&TitleString` automatically coerces to `&String` -> `&str`!
print_length(&title);
// Call String methods directly on TitleString via Deref method resolution:
title.push_str(" (2026 Edition)");
// Call slice method `.to_uppercase()` directly:
println!("Uppercase: {}", title.to_uppercase());
}
4. How Deref Coercion Works
Rust applies Deref Coercion automatically in three scenarios:
&Tto&UwhenT: Deref<Target = U>(e.g.&String&str).&mut Tto&mut UwhenT: DerefMut<Target = U>(e.g.&mut Vec<T>&mut [T]).&mut Tto&UwhenT: Deref<Target = U>(coercing a mutable reference to an immutable reference).
4. Common Mistakes & Pitfalls
Mistake 1: Misusing Deref for Object-Oriented Class Inheritance
The mistake: Implementing Deref on a UserStruct to deref into BaseStruct simply to inherit BaseStruct's fields or methods.
Why it's wrong: Deref is intended strictly for smart pointers and container wrappers (Box, Rc, Arc, String, Vec). Using Deref as a trick for OOP inheritance hides field origins, causes surprising method resolution collisions, and is considered an anti-pattern in Rust code reviews.
Incorrect (Anti-Pattern):
struct BaseUser { id: u64 }
struct AdminUser { base: BaseUser, permissions: Vec<String> }
// ❌ Anti-pattern: Misusing Deref for OOP-style inheritance!
impl Deref for AdminUser {
type Target = BaseUser;
fn deref(&self) -> &Self::Target { &self.base }
}
Fix:
// Correct: Use explicit field access or delegation traits
impl AdminUser {
pub fn id(&self) -> u64 { self.base.id }
}
Mistake 2: Method Name Collisions Between Wrapper and Inner Target
The mistake: Defining a method fn len(&self) on a custom smart pointer when Target also defines fn len(&self).
Why it's wrong: Method resolution checks the outer wrapper type FIRST before attempting Deref coercion. If the wrapper defines len(), calling wrapper.len() invokes the wrapper's method, masking the inner Target method.
Incorrect:
struct MySmartVec<T> { data: Vec<T> }
impl<T> MySmartVec<T> {
// ❌ Masks `Vec::len`! `my_smart_vec.len()` calls this instead of inner Vec::len.
pub fn len(&self) -> &'static str { "custom wrapper" }
}
Fix:
// Avoid method name collisions on smart pointer wrappers; prefer inner delegation
Mistake 3: Forgetting Deref Requirement when Implementing DerefMut
The mistake: Writing impl DerefMut for MyType without first implementing Deref.
Why it's wrong: In the standard library, DerefMut has Deref as a supertrait (pub trait DerefMut: Deref). You cannot implement mutable dereferencing without first defining immutable dereferencing.
5. Practice Exercises
Exercise 1: Multi-Tier Deref Coercion in an Embedded Telemetry Buffer
Scenario: In an embedded telemetry network, UART packet frames are wrapped inside a TelemetryFrame struct containing metadata (header ID) and a byte payload (Vec<u8>). Implement Deref<Target = Vec<u8>> and DerefMut for TelemetryFrame. Demonstrate multi-tier deref coercion (&TelemetryFrame &Vec<u8> &[u8]) by passing a &TelemetryFrame reference directly to a function expecting a byte slice &[u8], calling slice methods (.len(), .split_at()), and modifying inner bytes in-place via DerefMut.
Answer
Implementation
use std::ops::{Deref, DerefMut};
/// Embedded telemetry frame holding header metadata and dynamic byte payload
#[derive(Debug, PartialEq, Eq)]
pub struct TelemetryFrame {
pub header_id: u32,
payload: Vec<u8>,
}
impl TelemetryFrame {
pub fn new(header_id: u32, payload: Vec<u8>) -> Self {
Self { header_id, payload }
}
}
// 1. Implement `Deref` to expose the inner `Vec<u8>` payload
impl Deref for TelemetryFrame {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.payload
}
}
// 2. Implement `DerefMut` to support mutable byte slice operations
impl DerefMut for TelemetryFrame {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.payload
}
}
/// Function expecting a primitive slice `&[u8]`
pub fn verify_packet_prefix(slice: &[u8]) -> bool {
slice.starts_with(&[0xAA, 0xBB])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multi_tier_deref_coercion() {
let frame = TelemetryFrame::new(101, vec![0xAA, 0xBB, 0x01, 0x02]);
// Multi-tier Deref Coercion: &TelemetryFrame -> &Vec<u8> -> &[u8]
assert!(verify_packet_prefix(&frame));
// Method resolution automatically forwards `.len()` and `.split_at()` to Vec/slice
assert_eq!(frame.len(), 4);
let (head, tail) = frame.split_at(2);
assert_eq!(head, &[0xAA, 0xBB]);
assert_eq!(tail, &[0x01, 0x02]);
}
#[test]
fn test_deref_mut_in_place_modification() {
let mut frame = TelemetryFrame::new(102, vec![0x00, 0x11, 0x22]);
// Mutate inner payload via DerefMut index operator & push method
frame[0] = 0xFF;
frame.push(0x33);
assert_eq!(&frame[..], &[0xFF, 0x11, 0x22, 0x33]);
assert_eq!(frame.len(), 4);
}
}
Technical Explanation
- Multi-Tier Deref Coercion: When passing
&frame(&TelemetryFrame) toverify_packet_prefix(&[u8]), Rust dereferencesTelemetryFrametoVec<u8>, and then recursively appliesVec<u8>'sDerefimplementation to yield&[u8]. This zero-cost conversion occurs at compile time without heap allocations or runtime overhead. - Transparent Method Delegation: Method calls like
frame.len()andframe.split_at(2)trigger Rust's method resolution logic: if the outer type (TelemetryFrame) does not define the method, Rust dereferences toVec<u8>and subsequently to[u8]slice methods. - In-Place Mutation with
DerefMut: ImplementingDerefMutenables mutable indexing (frame[0] = 0xFF) and calling mutating collection methods (frame.push(0x33)). Note thatDerefMutrequiresDerefas a supertrait (pub trait DerefMut: Deref).
Exercise 2: Implementing an Audited Access RAII Guard
Scenario: In high-integrity systems and embedded diagnostic tools, read and write accesses to shared data or configuration blocks must be monitored for audit trails. Implement a custom RAII guard struct AuditedGuard<'a, T> wrapping a mutable reference &'a mut T and a reference to an AuditTracker. Implement Deref and DerefMut for AuditedGuard to increment read and write counters inside AuditTracker whenever the underlying data is dereferenced for reading or writing.
Answer
Implementation
use std::cell::Cell;
use std::ops::{Deref, DerefMut};
/// Shared tracker recording access counts using interior mutability (`Cell`)
#[derive(Debug, Default)]
pub struct AuditTracker {
reads: Cell<usize>,
writes: Cell<usize>,
}
impl AuditTracker {
pub fn new() -> Self {
Self {
reads: Cell::new(0),
writes: Cell::new(0),
}
}
pub fn read_count(&self) -> usize {
self.reads.get()
}
pub fn write_count(&self) -> usize {
self.writes.get()
}
}
/// RAII Guard that audits read and write dereferences to wrapped data `T`
pub struct AuditedGuard<'a, T> {
data: &'a mut T,
tracker: &'a AuditTracker,
}
impl<'a, T> AuditedGuard<'a, T> {
pub fn new(data: &'a mut T, tracker: &'a AuditTracker) -> Self {
Self { data, tracker }
}
}
// 1. Implement `Deref` to audit read accesses and return `&T`
impl<'a, T> Deref for AuditedGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.tracker.reads.set(self.tracker.read_count() + 1);
self.data
}
}
// 2. Implement `DerefMut` to audit write accesses and return `&mut T`
impl<'a, T> DerefMut for AuditedGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.tracker.writes.set(self.tracker.write_count() + 1);
self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Eq)]
struct DeviceConfig {
baud_rate: u32,
enabled: bool,
}
#[test]
fn test_audited_guard_read_and_write_tracking() {
let tracker = AuditTracker::new();
let mut config = DeviceConfig {
baud_rate: 9600,
enabled: false,
};
{
let mut guard = AuditedGuard::new(&mut config, &tracker);
// Read access via Deref (field lookup triggers deref())
assert_eq!(guard.baud_rate, 9600);
assert_eq!(tracker.read_count(), 1);
// Write access via DerefMut (field mutation triggers deref_mut())
guard.baud_rate = 115200;
guard.enabled = true;
assert_eq!(tracker.write_count(), 2);
// Read access via Deref again
assert!(guard.enabled);
assert_eq!(tracker.read_count(), 2);
}
// Verify target state mutated correctly after guard leaves scope
assert_eq!(config.baud_rate, 115200);
assert_eq!(config.enabled, true);
assert_eq!(tracker.read_count(), 2);
assert_eq!(tracker.write_count(), 2);
}
}
Technical Explanation
- Custom Smart Pointer Guard Pattern:
AuditedGuard<'a, T>mimics standard library RAII types likeMutexGuardorRefMut. It temporarily owns an exclusive borrow&'a mut T. - Interior Mutability in
Deref: Thederef(&self)signature takes an immutable shared reference&self. To incrementtracker.readswithout requiring&mut self,AuditTrackerutilizesstd::cell::Cell<usize>, allowing interior mutability through shared references. - Implicit Operator Hooking: Field accesses (
guard.baud_rate) and method calls automatically invokederef()orderef_mut()under the hood, seamlessly instrumenting field reads and writes without altering user-facing access syntax. - Lifetime Safety: Tying the lifetime
'aofdataandtrackerinsideAuditedGuard<'a, T>ensures statically that the guard cannot outlive the underlying resource or tracker.
Exercise 3: Protecting Domain Invariants with Read-Only Deref Coercion
Scenario: In network protocol handlers and IoT edge gateways, newtype wrappers like ValidatedHostname guarantee that string data conforms to domain formatting rules (e.g. non-empty, ASCII alphanumeric/hyphens/dots, maximum 63 characters). Implementing Deref<Target = str> enables convenient read-only operations and Deref coercion to &str. Explain and demonstrate why DerefMut must be deliberately omitted to prevent caller code from corrupting validated domain invariants post-construction.
Answer
Implementation
use std::ops::Deref;
/// Validated network hostname newtype guaranteeing valid domain format
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidatedHostname(String);
impl ValidatedHostname {
/// Constructs a ValidatedHostname if input satisfies all formatting rules
pub fn parse(input: &str) -> Result<Self, &'static str> {
if input.is_empty() {
return Err("Hostname cannot be empty");
}
if input.len() > 63 {
return Err("Hostname exceeds maximum length of 63 characters");
}
if !input.chars().all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-') {
return Err("Hostname contains invalid characters");
}
Ok(Self(input.to_lowercase()))
}
}
// 1. Implement `Deref` to `str` for transparent read-only string access
impl Deref for ValidatedHostname {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// NOTE: `DerefMut` is INTENTIONALLY OMITTED to preserve domain validation invariants!
/// External DNS lookup helper expecting a standard string slice `&str`
pub fn resolve_dns_query(domain: &str) -> String {
format!("RESOLVED_{}", domain.to_uppercase())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_hostname_construction_and_deref_coercion() {
let host = ValidatedHostname::parse("Sensor-Node-01.local").expect("Valid hostname");
// Deref coercion: passing &ValidatedHostname where &str is expected
let result = resolve_dns_query(&host);
assert_eq!(result, "RESOLVED_SENSOR-NODE-01.LOCAL");
// Invoke read-only `str` methods directly on ValidatedHostname via Deref
assert_eq!(host.len(), 20);
assert!(host.contains("node"));
assert!(host.ends_with(".local"));
assert_eq!(&host[..6], "sensor");
}
#[test]
fn test_hostname_validation_rejects_invalid_inputs() {
assert!(ValidatedHostname::parse("").is_err());
assert!(ValidatedHostname::parse("bad host name").is_err());
assert!(ValidatedHostname::parse("invalid_symbol!@#").is_err());
}
}
Technical Explanation
- Newtype Pattern with Read-Only Deref: Wrapping
StringinValidatedHostname(String)encapsulates the inner field. ImplementingDeref<Target = str>allows callers to read data using standard slice functions without needing redundant getters likepub fn as_str(&self) -> &str. - Invariant Safety via Omission: If
DerefMutwere implemented targetingStringorstr, caller code could executehost.push_str(" invalid space!")orhost.clear(), violating the structural validation invariants established duringparse(). OmittingDerefMutstatically guarantees immutability through dereferencing. - Coercion Target Selection:
ValidatedHostnametargetsstr(type Target = str;) rather thanString. Dereferencing tostris idiomatic in Rust because&strprovides all read-only string algorithms (.len(),.find(),.to_uppercase(), slicing) without exposing allocation methods (.reserve(),.shrink_to_fit()).
6. Related Terms
- Smart Pointers (
Box,Rc,Arc) — Wrapper types that rely heavily onDeref. - Operator Overloading — Customizing built-in operators via
std::ops. AsRef/AsMut— Explicit reference conversion traits (contrasted with implicitDeref).Borrow/BorrowMut— Consistency-guaranteed reference conversion traits.DerefandDerefMutTraits — Related concept:DerefandDerefMutTraits.- Deref Coercion — Related concept: Deref Coercion.
7. Key Takeaways
Deref(fn deref(&self) -> &Target) andDerefMut(fn deref_mut(&mut self) -> &mut Target) customize the*dereference operator.- They enable Deref Coercion, automatically converting
&SmartPointer<T>to&T(and&String&str,&Vec<T>&[T]). - Method calls (
wrapper.method()) automatically resolve onTargetifwrapperdoes not define a method with that name. Derefshould ONLY be implemented for smart pointers and container wrappers — never as a trick for OOP class inheritance.