401: Deref and Deref Coercions
Tutorial Video
Text description (accessibility)
This video demonstrates the "401: Deref and Deref Coercions" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Rust's ownership system produces many wrapper types: `Box<T>`, `Arc<T>`, `String`, `Vec<T>`. Key difference from OCaml: 1. **Implicit vs. explicit**: Rust inserts deref coercions automatically; OCaml requires explicit conversion at every call site.
Tutorial
The Problem
Rust's ownership system produces many wrapper types: Box<T>, Arc<T>, String, Vec<T>. Without deref coercions, using these types would require explicit unwrapping everywhere — (*my_box).some_method(), (&my_string).as_str(). The Deref trait and Rust's deref coercion rules automatically convert &Box<T> to &T, &String to &str, and &Vec<T> to &[T] when the compiler needs to. This makes functions accepting &str work seamlessly with String, Box<String>, Arc<String>, and any other type that dereferences to str.
Deref coercions are fundamental to std: they explain why Box<T> behaves like T, why String works where &str is expected, and how custom smart pointers integrate with the rest of the language.
🎯 Learning Outcomes
Deref trait and its type Target associated type* automatically at type boundariesDeref<Target = T> makes a type behave like a pointer to TArc<String> → String → str through multiple coercionsDerefMut for mutable dereferences and its role in transparent mutationCode Example
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
fn use_str(s: &str) {
println!("String: {}", s);
}
fn main() {
let boxed = MyBox(String::from("hello"));
use_str(&boxed); // Auto-coerces: &MyBox<String> -> &String -> &str
}Key Differences
DerefMut enables *my_box = new_value through the smart pointer; OCaml uses explicit setter functions or mutable record fields.Box, Arc, Rc, Mutex guards, and custom types all "disappear" at use sites; OCaml smart pointers require explicit unwrapping.OCaml Approach
OCaml has no automatic coercions — all conversions are explicit. Buffer.contents buf to get a string from a buffer, String.to_bytes s for byte conversion, etc. OCaml's objects support subtype coercion ((obj :> base_class_type)) but this is structural subtyping, not deref-based. The programmer writes explicit conversion functions where Rust would insert implicit deref coercions.
Full Source
#![allow(clippy::all)]
//! Deref and Deref Coercions
//!
//! Automatic reference conversions that let smart pointers and owned types
//! work seamlessly with borrowed slices and str.
use std::fmt;
use std::ops::{Deref, DerefMut};
/// A custom smart pointer implementing Deref.
///
/// This demonstrates how any type can behave like a reference to its inner value.
pub struct MyBox<T>(T);
impl<T> MyBox<T> {
/// Creates a new MyBox wrapping the given value.
pub fn new(x: T) -> MyBox<T> {
MyBox(x)
}
/// Consumes the box and returns the inner value.
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for MyBox<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: fmt::Display> fmt::Display for MyBox<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MyBox({})", self.0)
}
}
/// Approach 1: Accept borrowed str, works with String, &str, Box<String>, etc.
///
/// This demonstrates how deref coercion enables flexible APIs.
pub fn string_length(s: &str) -> usize {
s.len()
}
/// Approach 2: Accept borrowed slice, works with Vec<T>, arrays, Box<Vec<T>>, etc.
pub fn slice_sum(nums: &[i32]) -> i32 {
nums.iter().sum()
}
/// Approach 3: Generic function that works with anything that derefs to T.
///
/// The `AsRef<T>` trait is similar to Deref but more explicit about intent.
pub fn generic_len<S: AsRef<str>>(s: S) -> usize {
s.as_ref().len()
}
/// Demonstrates multi-level deref chain: Box<String> -> String -> str.
pub fn process_boxed_string(boxed: &Box<String>) -> String {
// Deref chain: &Box<String> -> &String -> &str
// We can call str methods directly!
boxed.to_uppercase()
}
/// Demonstrates mutable deref: pushing to a boxed Vec.
pub fn push_to_boxed_vec(boxed: &mut Box<Vec<i32>>, value: i32) {
// DerefMut allows mutable access
boxed.push(value);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mybox_deref() {
let b = MyBox::new(42);
// Explicit deref
assert_eq!(*b, 42);
}
#[test]
fn test_mybox_into_inner() {
let b = MyBox::new(String::from("hello"));
let s = b.into_inner();
assert_eq!(s, "hello");
}
#[test]
fn test_string_coercion() {
let owned = String::from("hello");
// &String coerces to &str
assert_eq!(string_length(&owned), 5);
}
#[test]
fn test_boxed_string_coercion() {
let boxed = Box::new(String::from("world"));
// &Box<String> coerces through: Box -> String -> str
assert_eq!(string_length(&boxed), 5);
}
#[test]
fn test_mybox_string_coercion() {
let my_box = MyBox::new(String::from("custom"));
// &MyBox<String> -> &String -> &str (two-level coercion)
assert_eq!(string_length(&my_box), 6);
}
#[test]
fn test_vec_to_slice_coercion() {
let v = vec![1, 2, 3, 4, 5];
// &Vec<i32> coerces to &[i32]
assert_eq!(slice_sum(&v), 15);
}
#[test]
fn test_boxed_vec_coercion() {
let boxed_vec = Box::new(vec![10, 20, 30]);
// &Box<Vec<i32>> coerces through: Box -> Vec -> [i32]
assert_eq!(slice_sum(&boxed_vec), 60);
}
#[test]
fn test_generic_asref() {
assert_eq!(generic_len("literal"), 7);
assert_eq!(generic_len(String::from("owned")), 5);
assert_eq!(generic_len(&String::from("borrowed")), 8);
}
#[test]
fn test_process_boxed_string() {
let boxed = Box::new(String::from("hello"));
assert_eq!(process_boxed_string(&boxed), "HELLO");
}
#[test]
fn test_deref_mut() {
let mut b = MyBox::new(vec![1, 2, 3]);
b.push(4); // DerefMut allows calling Vec methods
assert_eq!(*b, vec![1, 2, 3, 4]);
}
#[test]
fn test_push_to_boxed_vec() {
let mut boxed = Box::new(vec![1, 2]);
push_to_boxed_vec(&mut boxed, 3);
assert_eq!(*boxed, vec![1, 2, 3]);
}
#[test]
fn test_method_resolution_through_deref() {
let boxed = MyBox::new(String::from("test"));
// .len() is resolved through deref chain: MyBox -> String -> str
assert_eq!(boxed.len(), 4);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mybox_deref() {
let b = MyBox::new(42);
// Explicit deref
assert_eq!(*b, 42);
}
#[test]
fn test_mybox_into_inner() {
let b = MyBox::new(String::from("hello"));
let s = b.into_inner();
assert_eq!(s, "hello");
}
#[test]
fn test_string_coercion() {
let owned = String::from("hello");
// &String coerces to &str
assert_eq!(string_length(&owned), 5);
}
#[test]
fn test_boxed_string_coercion() {
let boxed = Box::new(String::from("world"));
// &Box<String> coerces through: Box -> String -> str
assert_eq!(string_length(&boxed), 5);
}
#[test]
fn test_mybox_string_coercion() {
let my_box = MyBox::new(String::from("custom"));
// &MyBox<String> -> &String -> &str (two-level coercion)
assert_eq!(string_length(&my_box), 6);
}
#[test]
fn test_vec_to_slice_coercion() {
let v = vec![1, 2, 3, 4, 5];
// &Vec<i32> coerces to &[i32]
assert_eq!(slice_sum(&v), 15);
}
#[test]
fn test_boxed_vec_coercion() {
let boxed_vec = Box::new(vec![10, 20, 30]);
// &Box<Vec<i32>> coerces through: Box -> Vec -> [i32]
assert_eq!(slice_sum(&boxed_vec), 60);
}
#[test]
fn test_generic_asref() {
assert_eq!(generic_len("literal"), 7);
assert_eq!(generic_len(String::from("owned")), 5);
assert_eq!(generic_len(&String::from("borrowed")), 8);
}
#[test]
fn test_process_boxed_string() {
let boxed = Box::new(String::from("hello"));
assert_eq!(process_boxed_string(&boxed), "HELLO");
}
#[test]
fn test_deref_mut() {
let mut b = MyBox::new(vec![1, 2, 3]);
b.push(4); // DerefMut allows calling Vec methods
assert_eq!(*b, vec![1, 2, 3, 4]);
}
#[test]
fn test_push_to_boxed_vec() {
let mut boxed = Box::new(vec![1, 2]);
push_to_boxed_vec(&mut boxed, 3);
assert_eq!(*boxed, vec![1, 2, 3]);
}
#[test]
fn test_method_resolution_through_deref() {
let boxed = MyBox::new(String::from("test"));
// .len() is resolved through deref chain: MyBox -> String -> str
assert_eq!(boxed.len(), 4);
}
}
Deep Comparison
OCaml vs Rust: Deref Coercions
Side-by-Side Code
OCaml — Manual unwrapping (no auto-coercion)
module Box = struct
type 'a t = { value: 'a }
let create v = { value = v }
let deref { value } = value
end
let use_string (s : string) =
Printf.printf "String: %s\n" s
let () =
let boxed = Box.create "hello" in
use_string (Box.deref boxed) (* Explicit deref required *)
Rust — Automatic deref coercion
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
fn use_str(s: &str) {
println!("String: {}", s);
}
fn main() {
let boxed = MyBox(String::from("hello"));
use_str(&boxed); // Auto-coerces: &MyBox<String> -> &String -> &str
}
Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Automatic coercion | None — explicit unwrap required | Deref chain followed automatically |
| Smart pointer ergonomics | Must call accessor function | Methods resolve through deref chain |
| String types | Single string type | String (owned) / &str (borrowed) |
| Custom containers | Manual accessor pattern | Implement Deref trait |
| Coercion depth | N/A | Unlimited chain: Box<Vec<String>> → &str |
| Runtime cost | None (no coercion exists) | Zero — compile-time transformation |
| Mutability | Separate mutable wrapper | DerefMut for mutable access |
The Deref Chain
// Compiler follows Deref impls until types match:
&Box<String> → &String → &str
│ │ │
└── Box::deref ┘ │
└── String::deref ┘
In OCaml, you'd need:
let inner = Box.deref (StringBox.deref boxed_string_box) in
use_string inner (* Two explicit unwraps *)
Key Differences
1. Automatic vs Explicit
OCaml: Every wrapper requires explicit unwrapping
let x = Box.deref (Box.create 42) in
print_int x
Rust: Compiler inserts derefs automatically
let x = MyBox(42);
println!("{}", *x); // Or even: println!("{}", x); with Display
2. Method Resolution
OCaml: Methods don't "pass through" wrappers
let s = Box.create "hello" in
String.length (Box.deref s) (* Must unwrap first *)
Rust: Methods resolve through deref chain
let s = MyBox(String::from("hello"));
s.len() // Finds str::len() through MyBox -> String -> str
3. API Flexibility
OCaml: Functions must accept the exact type
let print_string (s : string) = ...
(* Cannot pass Box.t without unwrapping *)
Rust: Functions accepting &str work with many types
fn greet(s: &str) { ... }
greet(&String::from("hi")); // &String -> &str
greet(&Box::new("hi".into())); // &Box<String> -> &String -> &str
greet(&MyBox::new("hi".into())); // &MyBox<String> -> &String -> &str
5 Takeaways
The compiler follows Deref implementations at compile time — no runtime
dispatch, no hidden costs.
No magic means no surprises. What you write is what you get.
Implement Deref<Target=T> and your type behaves like &T everywhere.
&str / &[T] pattern enables universal APIs.**Write functions accepting borrowed slices; they work with all owning types.
box.len() where box: MyBox<String> finds str::len() automatically.
Exercises
Counted<T> smart pointer that wraps T and counts how many times it has been dereferenced. Implement Deref<Target = T> and DerefMut. Write tests verifying the count increments.fn bytes_length(s: &[u8]) -> usize. Show that it accepts Vec<u8>, Box<Vec<u8>>, and a custom ByteBuffer implementing Deref<Target = Vec<u8>> through successive coercions.Pool<T> with clone_ref returning a PoolRef<T> that implements Deref<Target = T>. Use an Arc<T> internally for the value, demonstrating how smart pointers compose with deref.