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.
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:
// 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:
String→Deref<Target = str>→&Stringwird 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>→*guardgreift 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.
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:
&Box<String>→&StringviaBox'sDeref<Target = String>.&String→&strviaString'sDeref<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.
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:
- Probiere die Methode direkt auf dem Wert-Typ (
T). - Wenn nicht: probiere auf
&T,&mut T(Auto-Ref). - 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:
&Twird 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, wirdTexakt aus dem Argument inferiert — keine Coercion zur Deref-Kette. - In Trait-Implementationen-Matching.
impl Display for Xgilt nicht automatisch für&Box<X>. - Bei
as-Casts. Coercion ist Implicit,asist Explicit — anderes System.
Mehr dazu im Stolperfallen-Insight.
Custom Deref-Impls
Du kannst Deref selbst implementieren — das macht eigene Smart-Pointer-Typen ergonomisch:
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)
| Wrapper | Deref-Target | Praxis-Bedeutung |
|---|---|---|
String | str | &String → &str |
Vec<T> | [T] | &Vec<T> → &[T] |
Box<T> | T | Smart-Pointer transparent |
Rc<T> | T | Reference-Counted, gleicher Zugriff |
Arc<T> | T | Atomic-Rc, gleicher Zugriff |
Cow<'a, T> | T | Borrowed/Owned uniform |
MutexGuard<T> | T | wirkt wie &T (DerefMut: &mut T) |
RwLockReadGuard<T> | T | wirkt wie &T |
RwLockWriteGuard<T> | T | wirkt wie &mut T |
Ref<T> (von RefCell) | T | wirkt wie &T |
RefMut<T> | T | wirkt wie &mut T |
PathBuf | Path | analog zu String/str |
OsString | OsStr | analog |
CString | CStr | analog |
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
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
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
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
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
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
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
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
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
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]
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
- The Rust Book – Treating Smart Pointers Like Regular References with the Deref Trait
- std::ops::Deref
- std::ops::DerefMut
- Rust API Guidelines – Smart Pointer