Wenn du eine Referenz hast, willst du irgendwann auf den Wert dahinter zugreifen. Rust bietet dafür zwei Wege: explizit mit dem *-Operator, und implizit durch Auto-Deref. Auto-Deref ist eines der mächtigsten Werkzeuge der Sprache — es macht &String und &str an Funktions-Schnittstellen austauschbar, lässt Method-Calls über Smart-Pointer wie Box<T> oder Rc<T> ohne Sonderbehandlung funktionieren, und sorgt dafür, dass du r.len() schreiben kannst statt (*r).len(). Dieser Artikel zeigt, wie Dereferenzierung im Detail funktioniert, was der Deref-Trait macht, und welche Grenzen die Coercion hat.

Hinweis: Der Artikel berührt einige Themen, die ihr eigenes Kapitel haben — Traits (das Deref-Trait selbst), Generics (Box<T>, Rc<T>) und Lifetimes ('a). Die Syntax wird hier in Kontext gezeigt; die zugehörigen Tiefen-Kapitel kommen ab Kap 14.

Der *-Operator

*r ist der Dereference-Operator — er greift auf den Wert hinter einer Referenz zu:

Rust Explizite Dereferenzierung
fn main() {
    let x = 5;
    let r: &i32 = &x;
    assert_eq!(*r, 5);            // *r ergibt den i32-Wert hinter r
    assert_eq!(r, &5);            // r ist die Referenz selbst
}

Bei Mutation eines Werts hinter &mut T braucht man * explizit:

Rust Mutation durch &mut
fn main() {
    let mut x = 5;
    let r: &mut i32 = &mut x;
    *r = 10;                       // Wert hinter r auf 10 setzen
    *r += 1;                       // Wert hinter r um 1 erhöhen
    assert_eq!(x, 11);
}

*r = 10 schreibt in den Wert (x). Ohne * würde r = ... versuchen, die Referenz selbst neu zu binden — was Compile-Fehler ist.

Auto-Deref bei Method-Calls

Methoden-Aufrufe machen Dereferenzierung automatisch. Du schreibst nie (*r).methode(), immer r.methode():

Rust Methode auf Referenz
fn main() {
    let s = String::from("Hallo");
    let r: &String = &s;
    assert_eq!(r.len(), 5);        // r.len() = (*r).len() = String::len(r)
    assert_eq!(s.len(), 5);        // direkt — der Compiler macht es identisch
}

Der Compiler probiert beim Method-Lookup automatisch alle Dereferenzierungs-Stufen aus: r.len()(*r).len() → schließlich findet er String::len.

Auch verschachtelt

Rust Verschachtelte Dereferenzierung
fn main() {
    let s = String::from("Hi");
    let r: &&&String = &&&s;        // dreifache Referenz
    assert_eq!(r.len(), 2);          // Compiler dereferenziert 3-mal automatisch
}

Auch durch mehrere Indirektions-Ebenen findet der Compiler die Methode.

Der Deref-Trait

* ist normalerweise nur für „echte" Referenzen (&T, &mut T) definiert. Damit aber auch Smart-Pointer wie Box<T>, Rc<T>, String dereferenzierbar sind, gibt es den Deref-Trait:

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

Ein Typ, der Deref implementiert, sagt: „wenn du * auf mich anwendest, bekommst du &Target".

String implementiert Deref<Target = str>

Rust String → str
fn main() {
    let s: String = String::from("Hi");
    let r: &str = &*s;             // *s gibt str, &*s gibt &str
    // Oder mit Auto-Deref:
    let r2: &str = &s;             // implizite Deref-Coercion
    assert_eq!(r, "Hi");
    assert_eq!(r2, "Hi");
}

String ist im Wesentlichen ein „besseres &str mit Heap-Allocation" — und die Deref-Implementierung macht das transparent.

Box, Rc, Arc

Rust Smart-Pointer-Deref
use std::rc::Rc;

fn main() {
    let b: Box<i32> = Box::new(42);
    let r: &i32 = &*b;             // Box dereferenzieren
    assert_eq!(*r, 42);
    assert_eq!(*b, 42);            // direkt — auch ok

    let rc: Rc<String> = Rc::new(String::from("Hi"));
    assert_eq!(rc.len(), 2);        // Auto-Deref durch Rc, dann String → str
}

Box<T> und Rc<T> implementieren Deref<Target = T>. Du kannst sie wie normale Referenzen behandeln — der Compiler ruft die deref-Methode automatisch auf.

Deref-Coercion bei Funktions-Argumenten

Eine der mächtigsten Anwendungen: bei Funktions-Calls dereferenziert der Compiler automatisch, wenn das den Typ passend macht.

Rust Deref-Coercion
fn drucken(s: &str) {
    println!("{s}");
}

fn main() {
    let owned = String::from("Hi");
    drucken(&owned);                // &String → &str (Coercion)
    drucken("Literal");              // &str — direkt
    drucken(&"Literal".to_string()); // &String → &str
}

Die Funktion erwartet &str. Aufrufer gibt &String. Der Compiler erkennt: String: Deref<Target = str> — also kann &String zu &str gecoerced werden. Eine kostenlose Konvertierung im Maschinencode (&str ist nur ein Pointer auf die Bytes plus Länge — beide schon in String vorhanden).

Was alles coerciert wird

VonNachMöglich, wenn
&T&UT: Deref<Target = U>
&mut T&mut UT: DerefMut<Target = U>
&mut T&UT: Deref<Target = U>

Die Regel ist transitiv. &String kann zu &str werden, &Box<String> zu &str (zwei Deref-Schritte), und so weiter.

DerefMut für mutable Coercion

DerefMut ist das Pendant für &mut:

Rust DerefMut
fn mutieren(s: &mut String) {
    s.push_str("!");
}

fn main() {
    let mut b: Box<String> = Box::new(String::from("Hi"));
    mutieren(&mut b);                // &mut Box<String> → &mut String
    println!("{b}");                  // "Hi!"
}

Box<T>: DerefMut<Target = T> — also &mut Box<String>&mut String.

Wann Auto-Deref NICHT greift

Drei Fälle:

Rust Kein Auto-Deref bei Mutation
fn main() {
    let mut x = 5;
    let r = &mut x;
    // r = 10;             // Fehler — würde die Referenz neu binden
    *r = 10;                // ok — schreibt in den Wert
}

Bei r = ... will der Compiler die Bindung r neu setzen, nicht den Wert hinter r. Für Mutation des Werts: *r = ....

2. Vergleich von Referenzen

Rust Vergleich
fn main() {
    let a = 5;
    let b = 5;
    let ra = &a;
    let rb = &b;

    // Bei == arbeiten beide Operanden auf demselben Level
    assert_eq!(ra, rb);             // Vergleicht die i32-Werte (Auto-Deref)
    assert_eq!(*ra, *rb);            // identisch — explizit
}

Hier funktioniert == durch den PartialEq-Impl für &i32, der intern vergleicht.

3. Generische Funktionen mit Trait-Bounds

Rust Bei generischen Bounds
use std::fmt::Display;

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

fn main() {
    let s = String::from("Hi");
    drucke(&s);              // T = &String — Display ist für &String impl
    drucke(s);                // T = String — Display ist für String impl
}

Bei generischen Bounds wird der konkrete Typ exakt geprüft. Deref-Coercion findet hier nicht automatisch statt (außer der Trait-Implementierung).

Wichtige Deref-Implementierungen in der Stdlib

TypDeref-TargetPraxis-Bedeutung
Stringstr&String&str
Vec<T>[T]&Vec<T>&[T]
Box<T>TSmart-Pointer wirkt wie Referenz
Rc<T>TReference-Counted, transparenter Zugriff
Arc<T>TAtomic-Rc, gleichermaßen transparent
Cow<'a, T>TBorrowed/Owned wirkt wie Borrow
MutexGuard<T>TMutex-Guard wirkt wie &mut T
RefMut<T>TRefCell-Mutable wirkt wie &mut T
PathBufPathanalog zu String/str
OsStringOsStranalog
CStringCStranalog

Alle diese Deref-Beziehungen machen die jeweils kompaktere Form (&str, &[T], &T, ...) zur idiomatischen Funktions-Signatur. Owner-Typen werden automatisch übergeben.

Custom Deref-Impl

Für eigene Smart-Pointer kannst du Deref implementieren:

Rust Eigener Wrapper
use std::ops::Deref;

struct MeinBox<T>(T);

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

fn main() {
    let b = MeinBox(String::from("Hi"));
    println!("{}", b.len());        // Auto-Deref: b → &String → &str → len()
    let r: &str = &b;                // Coercion
    println!("{r}");
}

Eine eigene Box-artige Struktur — durch Deref wirkt sie nach außen wie eine Referenz auf den inneren Wert.

Vorsicht bei Deref-Misuse

Deref ist kein „beliebiger Konvertierungs-Operator". Die Rust API Guidelines sagen klar: Deref nur für Smart-Pointer, nicht für „dieser Typ verhält sich ein bisschen wie der andere". Wer Deref missbraucht, schafft verwirrende APIs (z. B. wenn eine Methode überraschend durch Deref-Coercion gefunden wird).

Praxis: Dereferenzierung im echten Code

Library-API mit &str statt &String

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

fn main() {
    let lit = "user@host";
    let owned = String::from("alice@example.com");

    assert!(ist_email(lit));         // &str direkt
    assert!(ist_email(&owned));      // &String → &str via Deref-Coercion
}

Hier siehst du Deref-Coercion in seiner häufigsten Form: eine Funktion mit &str-Parameter akzeptiert String-Literale (direkt) und &String-Borrows (durch Coercion). Beide Varianten kosten nichts — bei Literalen entsteht überhaupt keine Konvertierung, bei &String ist es nur eine Header-Anpassung (Pointer + Länge bleiben gleich, die Kapazität wird weggelassen).

Diese Flexibilität ist genau das, was Library-APIs sympathisch macht: dein Aufrufer muss keine Konvertierungen herumschreiben, sondern übergibt seinen Wert direkt in der Form, in der er ihn hat. Clippy warnt bei &String-Parametern mit clippy::ptr_arg, und die Warnung ist berechtigt — abgesehen von ganz seltenen Fällen ist &str immer die bessere Wahl.

Slice-API mit &[T] statt &Vec<T>

Rust Slice-Pattern
pub fn summe(v: &[i32]) -> i32 {
    v.iter().sum()
}

fn main() {
    let owned = vec![1, 2, 3];
    let array = [10, 20, 30];

    assert_eq!(summe(&owned), 6);     // &Vec<i32> → &[i32]
    assert_eq!(summe(&array), 60);    // &[i32; 3] → &[i32]
    assert_eq!(summe(&owned[1..]), 5); // Sub-Slice direkt
}

Das &[T]-Pattern ist die generelle Version von &str: eine Funktion mit Slice-Parameter akzeptiert alle Varianten von Sequenzen — Vec (durch Coercion), fixed-size Arrays (durch Coercion), Sub-Slices direkt. Jede dieser Quellen wird auf das gleiche &[T]-Layout reduziert: Pointer plus Länge.

Bei API-Design ist die Faustregel klar: &[T] als Parameter, wo immer du nur lesen willst. Wenn du den ganzen Vec brauchst (etwa um ihn zu verändern oder zu konsumieren), nimm Vec<T> per Wert. Aber für reine Read-Operationen ist &[T] immer die flexiblere Wahl, ohne Performance-Nachteil.

Method-Calls über Box

Rust Box transparent
struct Datenbank {
    verbindungen: Vec<String>,
}

impl Datenbank {
    fn anzahl(&self) -> usize {
        self.verbindungen.len()
    }
}

fn main() {
    let db: Box<Datenbank> = Box::new(Datenbank {
        verbindungen: vec!["a".into(), "b".into()],
    });
    assert_eq!(db.anzahl(), 2);    // db.anzahl() funktioniert durch Auto-Deref
}

Smart-Pointer wie Box<T> zeichnen sich dadurch aus, dass sie syntaktisch wie ihre Inhalte aussehen. db.anzahl() funktioniert, obwohl anzahl eine Methode auf Datenbank ist und db ein Box<Datenbank> ist. Der Compiler probiert beim Method-Lookup automatisch alle Deref-Stufen — er findet Datenbank::anzahl und ruft sie auf der dereferenzierten Box auf.

Diese Transparenz ist der Grund, warum Smart-Pointer in Rust nicht wie in C++ mit .get()-Aufrufen herumhantieren müssen. Eine Box<T> fühlt sich wie ein T an, ein Rc<T> fühlt sich wie ein T an, ein Arc<T> ebenso — alle drei nutzen Deref-Coercion, um sich zu „verstecken".

Cow als Funktions-Parameter

Rust Cow akzeptiert beides
use std::borrow::Cow;

fn ausgabe(s: Cow<str>) {
    println!("{s}");          // Cow<str> → &str via Deref
}

fn main() {
    ausgabe(Cow::Borrowed("Hi"));
    ausgabe(Cow::Owned(String::from("Welt")));
}

Cow<str> ist „Clone-on-Write" — ein Container, der entweder ein Borrow oder ein Owned-Wert sein kann, je nachdem, ob bei der Erzeugung gerade kopiert werden musste. Für den Aufrufer von ausgabe ist das transparent: dank Deref-Coercion wird der Cow<str> zu &str, und die println!-Logik arbeitet auf der Sub-Sicht.

Cow ist nützlich bei Funktionen, die manchmal ihren Input modifizieren müssen, manchmal nicht — etwa beim Entfernen von Steuerzeichen oder beim Normalisieren von Whitespace. Im Best Case wird das Original durchgereicht (Borrow), nur bei tatsächlicher Änderung entsteht eine Allokation (Owned).

MutexGuard transparent

Rust Lock-Pattern
use std::sync::Mutex;

fn main() {
    let m = Mutex::new(vec![1, 2, 3]);
    {
        let mut guard = m.lock().unwrap();
        guard.push(4);                // Auto-Deref: MutexGuard → Vec<i32>
        guard.push(5);
        assert_eq!(guard.len(), 5);
    }
}

MutexGuard<T> ist ein klassischer Smart-Pointer, der über Deref und DerefMut das interne T transparent macht. Du rufst guard.push(4) auf, und obwohl guard syntaktisch ein MutexGuard ist, wird die Methode Vec::push(&mut self, ...) aufgerufen. Der Lock bleibt während der ganzen Operation gehalten — beim Block-Ende läuft Drop, und der Lock wird freigegeben.

Diese Transparenz ist nicht nur ergonomisch — sie ist auch sicher. Der Compiler kann nicht erlauben, dass du den inneren Wert aus dem Guard herausziehst, ohne den Lock zu halten. Solange du den Guard hältst, hast du exklusiven Zugriff; sobald er gedroppt ist, kannst du gar nicht mehr auf den Wert zugreifen.

Path-Argument mit AsRef

Rust Path-Flexibilität
use std::path::Path;

pub fn datei_existiert<P: AsRef<Path>>(p: P) -> bool {
    p.as_ref().exists()
}

fn main() {
    let s = String::from("/etc/hosts");
    assert!(datei_existiert(&s));     // &String → &Path via AsRef
    assert!(datei_existiert("/etc/hosts"));
    assert!(datei_existiert(Path::new("/etc/hosts")));
}

AsRef<Path> ist eine Trait-basierte Verallgemeinerung der Deref-Coercion. Während Deref nur die direkte Deref-Chain T -> Target kennt, kann AsRef<Path> für beliebige Quell-Typen implementiert sein, die sich „zu einem Path referenzieren lassen" — String, PathBuf, &str, &Path, und für viele Encoding-Wrapper.

In std::fs-APIs ist AsRef<Path> die idiomatische Wahl, weil sie das maximale Spektrum an Eingaben akzeptiert. File::open, fs::read_to_string, fs::write haben alle dieses Pattern. Wenn du selbst eine API designst, die Pfade entgegennimmt, ist die Faustregel: nimm impl AsRef<Path> statt &str oder &Path.

Rc für geteilten Zugriff

Rust Rc<T>
use std::rc::Rc;

struct Konfig { host: String, port: u16 }

fn host_von(k: &Konfig) -> &str {
    &k.host
}

fn main() {
    let cfg = Rc::new(Konfig { host: "localhost".into(), port: 8080 });
    let cfg2 = Rc::clone(&cfg);

    println!("{}", host_von(&cfg));   // &Rc<Konfig> → &Konfig
    println!("{}", host_von(&cfg2));
}

&Rc<Konfig> wird durch Deref-Coercion zu &Konfig. Die Funktion host_von kennt den Smart-Pointer nicht — die Coercion ist transparent.

Eigener Smart-Pointer

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

// Smart-Pointer, der mitzählt, wie oft dereferenziert wurde
struct Counted<T> {
    wert: T,
    count: std::cell::Cell<u64>,
}

impl<T> Deref for Counted<T> {
    type Target = T;
    fn deref(&self) -> &T {
        self.count.set(self.count.get() + 1);
        &self.wert
    }
}

fn main() {
    let c = Counted { wert: String::from("Hi"), count: std::cell::Cell::new(0) };
    println!("{}", c.len());          // 1
    println!("{}", c.chars().count()); // 2
    println!("Zugriffe: {}", c.count.get());
}

Ein Smart-Pointer, der jeden Method-Call zählt. Klassisches Lehrbeispiel für Custom Deref.

Format-Helper mit Deref

Rust String oder &str
fn ausgeben(text: &str) {
    println!("[{:>10}]", text);
}

fn main() {
    let owned = String::from("Hallo");
    ausgeben(&owned);              // &String → &str
    ausgeben("Direkt");
    ausgeben(&"Konvertiert".to_string());
}

Eine &str-API akzeptiert dank Auto-Deref jede Form, die zu einem &str coerciert werden kann.

FAQ

Wann brauche ich * explizit?

Bei Mutation eines Werts durch eine &mut-Referenz (*r = neuer_wert), beim Vergleich zweier Werte hinter Referenzen (manchmal), und in seltenen Fällen, in denen der Compiler den Typ nicht eindeutig auflösen kann. In allen anderen Fällen reicht Auto-Deref.

Auto-Deref greift bei Method-Calls und in vielen Coercion-Punkten.

Bei r.method() probiert der Compiler r, *r, **r etc. durch. Bei Funktions-Argumenten wird die Deref-Chain transitiv gefolgt — &Box<String> kann zu &str werden.

&str als Parameter ist idiomatischer als &String.

Beide funktionieren dank Auto-Deref am Aufruf-Ort. Aber &str akzeptiert auch String-Literale ohne Allocation. Clippy warnt mit clippy::ptr_arg bei &String-Parametern.

Box verhält sich syntaktisch wie &T.

Method-Calls, Field-Access, Vergleiche — alles funktioniert auf Box<T> so, als wäre es ein T. Das ist die zentrale Eigenschaft eines Smart-Pointers in Rust.

Deref nur für Smart-Pointer implementieren.

Die Rust API Guidelines verbieten Deref als allgemeinen Konvertierungs-Operator. Es ist für „dieser Typ ist im Wesentlichen ein Wrapper um einen anderen Typ" gedacht. Für API-Konvertierungen lieber AsRef<T> oder From<T> verwenden.

DerefMut setzt Deref voraus.

Wer DerefMut implementiert, muss auch Deref implementieren. Die mutable Version ist eine Erweiterung der shared.

Method-Lookup ist clever, aber nicht magisch.

Der Compiler probiert beim r.method()-Lookup: r, &r, &mut r, *r, *&*r etc. — wenn eine passende Methode gefunden wird, nimmt er sie. Bei mehreren passenden gibt es Ambiguität — selten, aber löseware mit expliziter Type-Notation.

Auto-Deref macht keine Allocation.

&String → &str ist im Maschinencode oft eine 0-Instruktion-Operation (oder ein Register-Move). Der String hat schon Pointer + Länge — ein &str ist genau das.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu References & Borrowing

Zur Übersicht