ExamplesBy LevelBy TopicLearning Paths
401 Intermediate

401: Deref and Deref Coercions

Functional Programming

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

  • • Understand the Deref trait and its type Target associated type
  • • Learn how Rust's deref coercion inserts * automatically at type boundaries
  • • See how implementing Deref<Target = T> makes a type behave like a pointer to T
  • • Understand the deref chain: Arc<String>Stringstr through multiple coercions
  • • Learn DerefMut for mutable dereferences and its role in transparent mutation
  • Code 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

  • Implicit vs. explicit: Rust inserts deref coercions automatically; OCaml requires explicit conversion at every call site.
  • Chain depth: Rust applies deref coercions transitively to arbitrary depth; OCaml's explicit conversions never chain automatically.
  • Mutable deref: Rust's DerefMut enables *my_box = new_value through the smart pointer; OCaml uses explicit setter functions or mutable record fields.
  • Smart pointer integration: Rust's deref system lets 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);
        }
    }
    ✓ Tests Rust test suite
    #[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

    AspectOCamlRust
    Automatic coercionNone — explicit unwrap requiredDeref chain followed automatically
    Smart pointer ergonomicsMust call accessor functionMethods resolve through deref chain
    String typesSingle string typeString (owned) / &str (borrowed)
    Custom containersManual accessor patternImplement Deref trait
    Coercion depthN/AUnlimited chain: Box<Vec<String>>&str
    Runtime costNone (no coercion exists)Zero — compile-time transformation
    MutabilitySeparate mutable wrapperDerefMut 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

  • Deref coercion is implicit but predictable.
  • The compiler follows Deref implementations at compile time — no runtime dispatch, no hidden costs.

  • OCaml's explicit style has benefits too.
  • No magic means no surprises. What you write is what you get.

  • Custom smart pointers get full ergonomics in Rust.
  • Implement Deref<Target=T> and your type behaves like &T everywhere.

  • **The &str / &[T] pattern enables universal APIs.**
  • Write functions accepting borrowed slices; they work with all owning types.

  • Method resolution follows the deref chain.
  • box.len() where box: MyBox<String> finds str::len() automatically.

    Exercises

  • Custom smart pointer: Implement a 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.
  • Coercion chain: Write a function 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.
  • Rc-like pool: Implement a simplified 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.
  • Open Source Repos