ExamplesBy LevelBy TopicLearning Paths
118 Intermediate

Deref Coercions

Functional Programming

Tutorial

The Problem

Rust has many smart pointer types — String, Vec<T>, Box<T>, Arc<T>, Rc<T> — each wrapping a more primitive type. Without automatic conversion, every function accepting &str would reject &String, forcing callers to write .as_str() everywhere. Deref coercions solve this by letting the compiler insert implicit dereferences: &String becomes &str, &Vec<T> becomes &[T], transitively. This gives ergonomic APIs without sacrificing type safety.

🎯 Learning Outcomes

  • • Understand the Deref trait and how the compiler applies coercions automatically
  • • Learn to write functions that accept the most general borrowed form (&str, &[T])
  • • See how Box<T>, Arc<T>, and Rc<T> participate in coercion chains
  • • Implement Deref on a custom wrapper type to integrate with the coercion system
  • Code Example

    fn greet(name: &str) -> String {
        format!("Hello, {}!", name)
    }
    
    fn sum(data: &[i32]) -> i32 {
        data.iter().sum()
    }
    
    let s: String = String::from("Alice");
    greet(&s);                           // &String → &str  (one coercion)
    
    let boxed: Box<String> = Box::new(String::from("Bob"));
    greet(&boxed);                       // &Box<String> → &String → &str  (two coercions)
    
    let v: Vec<i32> = vec![1, 2, 3];
    sum(&v);                             // &Vec<i32> → &[i32]  (one coercion)

    Key Differences

  • Implicitness: Rust applies deref coercions automatically at argument positions; OCaml requires explicit conversion functions (e.g., Bytes.to_string).
  • Ownership model: Rust's coercions work on borrowed references (&T) — they never imply copying or ownership transfer.
  • Custom integration: Implementing Deref on a Rust newtype makes it participate in the coercion chain; OCaml has no equivalent mechanism.
  • Transitive chains: Rust follows the full chain (&Box<Vec<u8>>&Vec<u8>&[u8]); in OCaml all such conversions must be written explicitly.
  • OCaml Approach

    OCaml does not have deref coercions — every type conversion is explicit. However, OCaml's module system and polymorphism reduce the need for them: a function taking string already accepts any string value directly without wrapping. OCaml does have subtyping for object types and variant inheritance via polymorphic variants, but these are unrelated to pointer coercions.

    Full Source

    #![allow(clippy::all)]
    //! # Example 118: Deref Coercions
    //!
    //! The Rust compiler automatically dereferences smart pointers through the `Deref`
    //! trait so you rarely need explicit conversions. `&String` becomes `&str`,
    //! `&Vec<T>` becomes `&[T]`, and `&Box<T>` becomes `&T` — transitively.
    //!
    //! The idiomatic pattern is: **write functions that accept the most general borrow**
    //! (`&str`, `&[T]`) and let callers pass any owning or smart-pointer form for free.
    
    use std::ops::Deref;
    use std::sync::Arc;
    
    // ── Approach 1: Idiomatic — accept the most general borrowed form ─────────────
    
    /// Accepts `&str` — callers may pass `&String`, `&Box<String>`, `&Rc<String>`, …
    /// The compiler inserts as many `.deref()` calls as needed.
    pub fn greet(name: &str) -> String {
        format!("Hello, {}!", name)
    }
    
    /// Accepts `&[i32]` — callers may pass `&Vec<i32>`, `&[i32; N]`, `&Box<Vec<i32>>`, …
    pub fn sum(data: &[i32]) -> i32 {
        data.iter().sum()
    }
    
    /// Generic first element — works with any slice-like type via deref coercion.
    pub fn first<T>(items: &[T]) -> Option<&T> {
        items.first()
    }
    
    // ── Approach 2: Custom Deref implementation ───────────────────────────────────
    
    /// A newtype wrapper around `Vec<T>`.
    ///
    /// By implementing `Deref<Target = [T]>`, it participates in coercion chains:
    /// `&MyVec<T>` → `&[T]` automatically wherever `&[T]` is expected.
    pub struct MyVec<T>(Vec<T>);
    
    impl<T> MyVec<T> {
        pub fn new(v: Vec<T>) -> Self {
            Self(v)
        }
    }
    
    impl<T> Deref for MyVec<T> {
        type Target = [T];
    
        fn deref(&self) -> &[T] {
            &self.0
        }
    }
    
    /// Works because `&MyVec<i32>` coerces to `&[i32]` via our `Deref` impl.
    pub fn sum_my_vec(v: &MyVec<i32>) -> i32 {
        sum(v)
    }
    
    // ── Approach 3: Rc / Arc chains ───────────────────────────────────────────────
    
    /// Demonstrates `&Rc<String>` → `&String` → `&str` (two steps).
    pub fn shout(name: &str) -> String {
        format!("{}!", name.to_uppercase())
    }
    
    /// `&Arc<Vec<i32>>` → `&Vec<i32>` → `&[i32]` (two deref steps).
    pub fn sum_arc(data: &Arc<Vec<i32>>) -> i32 {
        sum(data)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::rc::Rc;
    
        // ── single-step coercions ─────────────────────────────────────────────────
    
        #[test]
        fn test_greet_str_literal() {
            assert_eq!(greet("Alice"), "Hello, Alice!");
        }
    
        #[test]
        fn test_greet_owned_string_coerces_to_str() {
            let name = String::from("Bob");
            assert_eq!(greet(&name), "Hello, Bob!"); // &String → &str
        }
    
        #[test]
        fn test_sum_vec_coerces_to_slice() {
            let v = vec![1, 2, 3, 4];
            assert_eq!(sum(&v), 10); // &Vec<i32> → &[i32]
        }
    
        #[test]
        fn test_sum_slice_literal() {
            assert_eq!(sum(&[10, 20, 30]), 60);
        }
    
        #[test]
        fn test_sum_empty() {
            assert_eq!(sum(&[]), 0);
        }
    
        // ── transitive / two-step coercions ──────────────────────────────────────
    
        #[test]
        fn test_greet_box_string_two_step_coercion() {
            // &Box<String> → &String → &str  (two automatic deref steps)
            let boxed: Box<String> = Box::new(String::from("Carol"));
            assert_eq!(greet(&boxed), "Hello, Carol!");
        }
    
        #[test]
        fn test_greet_rc_string_two_step_coercion() {
            // &Rc<String> → &String → &str
            let rc = Rc::new(String::from("Dave"));
            assert_eq!(shout(&rc), "DAVE!"); // uses shout, same coercion
        }
    
        #[test]
        fn test_sum_arc_vec_two_step_coercion() {
            // &Arc<Vec<i32>> → &Vec<i32> → &[i32]
            let arc = Arc::new(vec![5, 10, 15]);
            assert_eq!(sum_arc(&arc), 30);
        }
    
        // ── custom Deref ──────────────────────────────────────────────────────────
    
        #[test]
        fn test_custom_deref_sum() {
            let mv = MyVec::new(vec![5, 10, 15]);
            assert_eq!(sum_my_vec(&mv), 30); // &MyVec<i32> → &[i32]
        }
    
        #[test]
        fn test_custom_deref_first() {
            let mv = MyVec::new(vec![7, 8, 9]);
            assert_eq!(first(&mv), Some(&7)); // &MyVec<T> → &[T]
        }
    
        #[test]
        fn test_custom_deref_empty() {
            let mv: MyVec<i32> = MyVec::new(vec![]);
            assert_eq!(first(&mv), None);
        }
    
        // ── method resolution through deref ──────────────────────────────────────
    
        #[test]
        fn test_method_called_on_box_resolves_through_deref() {
            // Box<String> has no `.len()` method, but deref to String → str gives us one
            let boxed = Box::new(String::from("hello"));
            assert_eq!(boxed.len(), 5); // deref auto-applied for method calls too
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::rc::Rc;
    
        // ── single-step coercions ─────────────────────────────────────────────────
    
        #[test]
        fn test_greet_str_literal() {
            assert_eq!(greet("Alice"), "Hello, Alice!");
        }
    
        #[test]
        fn test_greet_owned_string_coerces_to_str() {
            let name = String::from("Bob");
            assert_eq!(greet(&name), "Hello, Bob!"); // &String → &str
        }
    
        #[test]
        fn test_sum_vec_coerces_to_slice() {
            let v = vec![1, 2, 3, 4];
            assert_eq!(sum(&v), 10); // &Vec<i32> → &[i32]
        }
    
        #[test]
        fn test_sum_slice_literal() {
            assert_eq!(sum(&[10, 20, 30]), 60);
        }
    
        #[test]
        fn test_sum_empty() {
            assert_eq!(sum(&[]), 0);
        }
    
        // ── transitive / two-step coercions ──────────────────────────────────────
    
        #[test]
        fn test_greet_box_string_two_step_coercion() {
            // &Box<String> → &String → &str  (two automatic deref steps)
            let boxed: Box<String> = Box::new(String::from("Carol"));
            assert_eq!(greet(&boxed), "Hello, Carol!");
        }
    
        #[test]
        fn test_greet_rc_string_two_step_coercion() {
            // &Rc<String> → &String → &str
            let rc = Rc::new(String::from("Dave"));
            assert_eq!(shout(&rc), "DAVE!"); // uses shout, same coercion
        }
    
        #[test]
        fn test_sum_arc_vec_two_step_coercion() {
            // &Arc<Vec<i32>> → &Vec<i32> → &[i32]
            let arc = Arc::new(vec![5, 10, 15]);
            assert_eq!(sum_arc(&arc), 30);
        }
    
        // ── custom Deref ──────────────────────────────────────────────────────────
    
        #[test]
        fn test_custom_deref_sum() {
            let mv = MyVec::new(vec![5, 10, 15]);
            assert_eq!(sum_my_vec(&mv), 30); // &MyVec<i32> → &[i32]
        }
    
        #[test]
        fn test_custom_deref_first() {
            let mv = MyVec::new(vec![7, 8, 9]);
            assert_eq!(first(&mv), Some(&7)); // &MyVec<T> → &[T]
        }
    
        #[test]
        fn test_custom_deref_empty() {
            let mv: MyVec<i32> = MyVec::new(vec![]);
            assert_eq!(first(&mv), None);
        }
    
        // ── method resolution through deref ──────────────────────────────────────
    
        #[test]
        fn test_method_called_on_box_resolves_through_deref() {
            // Box<String> has no `.len()` method, but deref to String → str gives us one
            let boxed = Box::new(String::from("hello"));
            assert_eq!(boxed.len(), 5); // deref auto-applied for method calls too
        }
    }

    Deep Comparison

    OCaml vs Rust: Deref Coercions

    Side-by-Side Code

    OCaml

    (* OCaml has no deref coercions — every conversion is explicit.
       You cannot pass a `bytes` where a `string` is expected. *)
    
    let greet (name : string) = Printf.printf "Hello, %s!\n" name
    
    let () =
      let s = "Alice" in
      greet s;                          (* only string literals / string values work *)
      let b = Bytes.of_string "Bob" in
      greet (Bytes.to_string b)         (* must convert explicitly *)
    

    Rust (idiomatic — coercions do the work)

    fn greet(name: &str) -> String {
        format!("Hello, {}!", name)
    }
    
    fn sum(data: &[i32]) -> i32 {
        data.iter().sum()
    }
    
    let s: String = String::from("Alice");
    greet(&s);                           // &String → &str  (one coercion)
    
    let boxed: Box<String> = Box::new(String::from("Bob"));
    greet(&boxed);                       // &Box<String> → &String → &str  (two coercions)
    
    let v: Vec<i32> = vec![1, 2, 3];
    sum(&v);                             // &Vec<i32> → &[i32]  (one coercion)
    

    Rust (custom Deref — same mechanism for user types)

    use std::ops::Deref;
    
    struct MyVec<T>(Vec<T>);
    
    impl<T> Deref for MyVec<T> {
        type Target = [T];
        fn deref(&self) -> &[T] { &self.0 }
    }
    
    fn sum_my_vec(v: &MyVec<i32>) -> i32 {
        sum(v)   // &MyVec<i32> coerces to &[i32] via Deref
    }
    

    Type Signatures

    ConceptOCamlRust
    String borrowstring (value, GC-managed)&str (slice reference)
    Owned stringstring (immutable)String (heap-owned)
    Slice borrow'a array&[T]
    Owned list'a listVec<T>
    Smart pointer'a refBox<T>, Rc<T>, Arc<T>
    Auto conversionnot availableDeref coercion (transitive)

    Key Insights

  • Explicit vs implicit conversion: OCaml requires Bytes.to_string, string_of_int, etc. at every boundary. Rust's Deref trait lets the compiler insert conversions silently when the types are related by a deref chain.
  • Transitive chains: &Box<String> becomes &str in two steps — the compiler finds the shortest coercion path automatically. OCaml has no equivalent; you compose converters by hand.
  • Write general functions once: A Rust function taking &str or &[T] is automatically compatible with String, Box<String>, Rc<String>, Vec<T>, Box<Vec<T>>, and any custom newtype that implements Deref. In OCaml you would need separate functions or a functor.
  • Custom types join the club: Implementing Deref for a newtype wrapper makes it transparently usable wherever the inner type is expected — no trait objects or conversion helpers needed.
  • No runtime cost: Deref coercions are purely compile-time. The emitted machine code is identical to writing the dereference by hand; there is no boxing, virtual dispatch, or allocation.
  • When to Use Each Style

    **Use idiomatic Rust (&str / &[T] parameters) when:** you want callers to freely pass any owning or borrowing form of the data — this is the normal case for library functions.

    **Use custom Deref when:** you have a newtype wrapper that should behave like its inner type in most contexts (e.g., MyVec, Path, OsStr).

    Exercises

  • Write a function print_all(items: &[impl Display]) and call it with &Vec<i32> and &[i32; 4] without any explicit conversion.
  • Create a LoggedVec<T> newtype that implements Deref<Target = Vec<T>> and logs every access.
  • Demonstrate a three-step coercion chain: &Box<String>&String&str in a single call.
  • Open Source Repos