Deref-Coercion ist eine der elegantesten Mechaniken in Rust — und gleichzeitig eine der am häufigsten missverstandenen. Sie sorgt dafür, dass du eine Box<String> an eine Funktion übergeben kannst, die ein &str erwartet. Dass du String-Methoden direkt auf einem Rc<String> aufrufen kannst. Dass Vec<T> als &[T] durchgereicht wird. All das geschieht automatisch durch eine Kette von Deref-Implementierungen, die der Compiler beim Bedarf entlangläuft. Wer das System versteht, sieht den ganzen „magischen" Komfort der Smart Pointers als logische Konsequenz weniger einfacher Regeln.

Was Deref-Coercion ist

Deref-Coercion ist eine Compile-Zeit-Konvertierung: wenn der Compiler ein &T erwartet, du aber ein &Wrapper lieferst und Wrapper: Deref<Target = T>, fügt der Compiler automatisch ein Deref::deref() ein.

Rust Mini-Demo
fn print_str(s: &str) {
    println!("{s}");
}

fn main() {
    let s: String = String::from("Hello");
    print_str(&s);   // → &String wird zu &str via Deref-Coercion
}

String implementiert Deref<Target = str>. Der Compiler sieht: du übergibst &String, die Funktion will &str. Statt zu meckern, fügt er den Deref-Aufruf automatisch ein: &s(&s).deref()&str. Die Konvertierung ist transparent.

Der Deref-Trait

Damit Coercion funktionieren kann, muss der Wrapper-Typ den Deref-Trait implementieren. Vereinfacht:

Rust Deref-Definition
// Stdlib (vereinfacht):
// pub trait Deref {
//     type Target: ?Sized;
//     fn deref(&self) -> &Self::Target;
// }

Target ist der Typ, auf den dereferenziert wird. deref(&self) -> &Target ist die Methode.

Beispiele aus der Stdlib:

  • StringDeref<Target = str>&String wird zu &str.
  • Vec<T>Deref<Target = [T]>&Vec<T> wird zu &[T].
  • Box<T>Deref<Target = T>&Box<T> wird zu &T.
  • Rc<T> / Arc<T>Deref<Target = T> → analog.
  • MutexGuard<T>Deref<Target = T>*guard greift auf den Wert zu.

Es gibt auch DerefMut für mutable Referenzen — analog mit &mut self&mut Target.

Die Deref-Kette

Der Compiler folgt der Deref-Kette transitiv, bis er den gewünschten Typ findet — oder bis es keine weitere Deref-Implementation gibt.

Rust Kette
fn print_str(s: &str) { println!("{s}"); }

fn main() {
    let boxed: Box<String> = Box::new(String::from("Hello"));
    print_str(&boxed);
    // Kette: &Box<String> → &String (via Box's Deref) → &str (via String's Deref)
}

Hier passiert zwei Schritte:

  1. &Box<String>&String via Box's Deref<Target = String>.
  2. &String&str via String's Deref<Target = str>.

Der Compiler probiert die Kette automatisch durch. Wenn der gewünschte Typ irgendwo darin liegt, klappt es. Wenn nicht, gibt es einen Type-Error.

Method-Resolution mit Deref

Eine zweite Anwendung von Deref ist die Method-Resolution. Wenn du eine Methode auf einem Wrapper-Typ aufrufst und der Typ selbst keine solche Methode hat, prüft der Compiler die Deref-Kette.

Rust Method auf Wrapper
fn main() {
    let s: Box<String> = Box::new(String::from("Hello World"));

    // .len() ist eine String/str-Methode, nicht Box's:
    let n = s.len();
    assert_eq!(n, 10);

    // Compiler-Schritt:
    // 1. s.len() — gibt es auf Box<String> direkt? Nein.
    // 2. (*s).len() — gibt es auf String? Ja (kommt von str via Deref oder direkt).
    //    → wird aufgerufen.
}

Der Compiler probiert die Method-Resolution mit jedem Deref-Schritt. Das ist der Grund, warum du String-Methoden auf Box<String> aufrufen kannst, ohne explizit zu dereferenzieren.

Die volle Resolution-Regel:

  1. Probiere die Methode direkt auf dem Wert-Typ (T).
  2. Wenn nicht: probiere auf &T, &mut T (Auto-Ref).
  3. Wenn nicht: probiere Deref::deref() und beginne von vorn mit dem Target.

Diese Regeln zusammen sind „Auto-Deref" — der Mechanismus, der Smart Pointers fast unsichtbar macht.

Wo Deref-Coercion greift

Deref-Coercion greift nur in bestimmten Kontexten:

  • Bei Funktions-Argumenten: &T wird automatisch zu &Target.
  • Bei Method-Aufrufen (siehe oben).
  • Bei Zuweisungen mit expliziter Type-Annotation: let x: &str = &owned;.

Deref-Coercion greift nicht:

  • Bei Generic-Type-Parametern. Wenn eine Funktion fn foo<T: Display>(x: T) ist, wird T exakt aus dem Argument inferiert — keine Coercion zur Deref-Kette.
  • In Trait-Implementationen-Matching. impl Display for X gilt nicht automatisch für &Box<X>.
  • Bei as-Casts. Coercion ist Implicit, as ist Explicit — anderes System.

Mehr dazu im Stolperfallen-Insight.

Custom Deref-Impls

Du kannst Deref selbst implementieren — das macht eigene Smart-Pointer-Typen ergonomisch:

Rust Custom Deref
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let b = MyBox(String::from("Hello"));

    // Deref-Coercion: &MyBox<String> → &String → &str
    fn show(s: &str) { println!("{s}"); }
    show(&b);

    // Method via Auto-Deref:
    let n = b.len();   // String::len via Deref-Kette
    assert_eq!(n, 5);

    // Explicit dereference:
    let s: &String = &*b;
    println!("{s}");
}

Mit Deref wirkt MyBox<T> wie ein transparenter Wrapper um T. Methoden, Funktions-Argumente — alles geht automatisch.

Vorsicht beim Custom-Deref: nutze ihn nur für echte Smart-Pointer-Typen (Box-artige Wrapper). Wenn dein Typ semantisch keine „Referenz auf etwas" ist (sondern z.B. eine Domain-Klasse), ist Deref ein Anti-Pattern — er weakened die Typ-Sicherheit. Die Stdlib-API-Guidelines warnen davor.

Vollständige Deref-Tabelle (Stdlib)

WrapperDeref-TargetPraxis-Bedeutung
Stringstr&String&str
Vec<T>[T]&Vec<T>&[T]
Box<T>TSmart-Pointer transparent
Rc<T>TReference-Counted, gleicher Zugriff
Arc<T>TAtomic-Rc, gleicher Zugriff
Cow<'a, T>TBorrowed/Owned uniform
MutexGuard<T>Twirkt wie &T (DerefMut: &mut T)
RwLockReadGuard<T>Twirkt wie &T
RwLockWriteGuard<T>Twirkt wie &mut T
Ref<T> (von RefCell)Twirkt wie &T
RefMut<T>Twirkt wie &mut T
PathBufPathanalog zu String/str
OsStringOsStranalog
CStringCStranalog

Diese Beziehungen erklären, warum so viele Stdlib-APIs mit &str, &[T], &Path arbeiten: durch Deref nehmen sie automatisch auch die owned-Varianten an. fn foo(s: &str) akzeptiert sowohl &str-Literale als auch &String als auch &Cow<str>.

Typische Stolperfallen

Deref-Coercion greift nicht bei Generic-Bounds

Rust Generic-Falle
use std::fmt::Display;

fn show<T: Display>(x: T) {
    println!("{x}");
}

fn main() {
    let s = String::from("Hello");

    // OK: &String implementiert Display → kein Coercion nötig
    show(&s);
    // OK: &str implementiert Display → exakter Match
    show(&s[..]);
    // OK: ownership statt Borrow
    show(s);
}

Bei Generics wird der Type exakt aus dem Argument inferiert. Deref-Coercion läuft nicht. Wenn du das Coercion-Verhalten willst, schreibe die Funktion mit &T-Argument (wo T der Target-Typ ist), nicht mit Generic-Parameter.

Method auf Smart-Pointer-Typ selbst vs. inneren Typ

Rust Methoden-Ambiguität
use std::rc::Rc;

fn main() {
    let r = Rc::new(String::from("Hello"));

    // Rc::clone vs. (*r).clone() — beide existieren!
    let r2 = Rc::clone(&r);            // explizit: Counter-Increment
    let s2 = (*r).clone();              // explizit: String-Klon (Deep-Copy)
    // r.clone() würde auch funktionieren — aber WAS macht es?
    // → ruft Rc::clone, weil das die direkte Methode ist (vor Deref-Suche).
    //   Aber das ist verwirrend! Daher Konvention: Rc::clone(&r).
    let _ = (r2, s2);
}

Beide Typen (Rc<String> und String) implementieren Clone. Bei r.clone() greift die Method-Resolution-Regel: erst der Typ selbst, dann der Deref-Target. Hier nimmt sie Rc::clone (Counter+1), nicht String::clone (Deep-Copy). Das ist die Stdlib-Konvention Rc::clone(&r) zu nutzen — explizit klar.

Deref-Coercion und String/&str

Rust String-Falle
fn take_str(s: &str) { println!("{s}"); }

fn main() {
    let owned = String::from("Hello");

    take_str(&owned);          // OK: Deref-Coercion &String → &str
    take_str(&owned[..]);      // OK: explizite Slice
    take_str(owned.as_str());  // OK: explizite Methode
    // take_str(owned);        // FEHLER: String != &str (kein Coercion bei owned)
}

Coercion läuft nur für Referenzen. Wenn die Funktion &str will und du einen owned String hast, geht es nur per &owned (mit Coercion) oder einer expliziten Methode.

Praxis: typische Deref-Beispiele

Funktion akzeptiert &str

Rust API-Form
pub fn ist_email(s: &str) -> bool {
    s.contains('@') && s.contains('.')
}

fn main() {
    // Aufrufer kann viele Formen übergeben — Deref macht's möglich:
    assert!(ist_email("alice@example.com"));        // &str literal

    let owned = String::from("bob@example.com");
    assert!(ist_email(&owned));                      // &String → &str

    use std::borrow::Cow;
    let cow: Cow<str> = Cow::Owned(String::from("charlie@example.com"));
    assert!(ist_email(&cow));                        // &Cow → &str
}

Die API nimmt &str. Der Aufrufer kann String-Literale, owned Strings, Cow, Box, Rc<String> übergeben — alles wird automatisch zu &str. Klassisches Stdlib-Pattern.

Method-Cascade über Smart-Pointer

Rust Method-Cascade
use std::rc::Rc;

fn main() {
    let r: Rc<String> = Rc::new(String::from("Hello World"));

    // Drei Method-Aufrufe — alle gehen über Auto-Deref:
    let len = r.len();              // Rc → String → str → .len()
    let upper = r.to_uppercase();   // analog → "HELLO WORLD"
    let first = r.chars().next();   // analog

    println!("{len}, {upper}, {first:?}");
}

Du kannst die &str/String-API direkt auf Rc<String> nutzen. Die zwei Deref-Steps sind unsichtbar.

MutexGuard wie &mut

Rust Guard-Deref
use std::sync::Mutex;

fn main() {
    let m = Mutex::new(vec![1, 2, 3]);

    {
        let mut guard = m.lock().unwrap();
        guard.push(4);          // Vec::push via DerefMut
        guard.sort();           // Vec::sort
        // guard wirkt komplett wie &mut Vec<i32>
    }

    println!("{:?}", *m.lock().unwrap());
}

MutexGuard implementiert Deref + DerefMut. Du nutzt es wie eine mutable Referenz auf den inneren Wert — alle Methoden des inneren Typs sind direkt verfügbar.

Box<dyn Trait>-Aufruf

Rust Box-dyn
trait Animal {
    fn name(&self) -> String;
}

struct Dog;
impl Animal for Dog {
    fn name(&self) -> String { String::from("Buddy") }
}

fn main() {
    let b: Box<dyn Animal> = Box::new(Dog);
    println!("{}", b.name());     // Auto-Deref: Box → dyn Animal → name()
}

Box<dyn Trait> wird via Deref automatisch zu &dyn Trait. Method-Calls funktionieren transparent.

Cow uniform behandelt

Rust Cow-Deref
use std::borrow::Cow;

fn len_of(s: &str) -> usize { s.len() }

fn main() {
    let owned: Cow<str> = Cow::Owned(String::from("Hello"));
    let borrowed: Cow<str> = Cow::Borrowed("World");

    // Beide werden via Deref zu &str:
    assert_eq!(len_of(&owned), 5);
    assert_eq!(len_of(&borrowed), 5);
}

Eine &Cow<str> wird automatisch zu &str. Die zugrundeliegende Variante (Owned oder Borrowed) ist transparent.

PathBuf zu Path-Funktionen

Rust Path-Beispiel
use std::path::{Path, PathBuf};

fn file_exists(p: &Path) -> bool {
    p.exists()
}

fn main() {
    let buf = PathBuf::from("/tmp/foo");
    // &PathBuf → &Path via Deref
    let _ = file_exists(&buf);
}

Stdlib-Path-APIs nehmen meist &Path. PathBuf wird automatisch via Deref konvertiert.

Vec<T> als &[T]

Rust Vec-Slice
fn sum(data: &[i32]) -> i32 {
    data.iter().sum()
}

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let s = sum(&v);              // &Vec<i32> → &[i32]
    assert_eq!(s, 15);

    let arr = [10, 20, 30];
    let s2 = sum(&arr);           // &Array → &[i32] (via Slice-Coercion, eng verwandt)
    assert_eq!(s2, 60);
}

Bibliothek-APIs nehmen typischerweise &[T]-Slices. Vec und Array werden automatisch konvertiert. Das macht die Stdlib-APIs maximal flexibel.

Besonderheiten

Deref-Coercion = automatische Konvertierung &Wrapper → &Target.

Der Compiler fügt Deref::deref()-Aufrufe ein, wenn du eine Referenz auf einen Wrapper übergibst und der erwartete Typ am Ende einer Deref-Kette liegt. Macht Smart Pointers transparent.

Deref ist ein Stdlib-Trait mit Target-Typ.

String: Deref<Target = str>, Vec<T>: Deref<Target = [T]>, Box<T>: Deref<Target = T> etc. Implementiere ihn nur für echte Smart-Pointer-Wrapper.

Die Deref-Kette läuft transitiv.

Box<String> → String → str. Mehrere Deref-Steps werden automatisch verknüpft. Endet, wenn der gewünschte Typ gefunden ist oder kein weiteres Deref greift.

Greift bei Funktions-Argumenten, Method-Resolution, Type-annotierten Zuweisungen.

Klassisch sichtbar bei fn foo(s: &str) mit String-Aufruf. Greift NICHT bei Generic-Bounds — dort wird der Type exakt inferiert.

DerefMut für mutable Refs.

Pendant zu Deref mit &mut self → &mut Target. MutexGuard, RefMut, Box mit DerefMut — alle wirken wie &mut T.

Auto-Deref bei Method-Resolution.

Wenn die Methode auf dem Wert-Typ nicht existiert, probiert der Compiler &T, &mut T, dann das Deref-Target. So funktioniert box.method() mit String-Methoden.

Rc::clone(&rc) statt rc.clone() ist Konvention.

Beide funktionieren, aber Rc::clone ist explizit. Bei der Method-Form ist nicht klar, ob Rc::clone (Counter +1) oder String::clone (Deep-Copy) gemeint ist. Stdlib-Konvention: die explizite Form.

Custom Deref nur für echte Smart-Pointer-Typen.

Wenn dein Typ semantisch nicht „eine Referenz auf etwas" ist, ist Deref ein Anti-Pattern (API-Guidelines warnen). Newtype-Wrapper z.B. sollten KEIN Deref haben — sonst verlieren sie ihre Typ-Sicherheit.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Smart Pointers

Zur Übersicht