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.

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
}

&str als Parameter ist flexibler als &String — beides funktioniert dank Deref-Coercion. Clippy warnt bei &String-Parametern (clippy::ptr_arg).

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
}

&[T] als Parameter akzeptiert Vec, Array und Sub-Slice. Drei Aufrufer-Varianten, eine Funktion.

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
}

db: Box<Datenbank> lässt sich wie Datenbank benutzen — alle Methoden funktionieren transparent.

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> dereferenziert zu &str. Sowohl borrowed als auch owned Variante werden im Aufruf transparent behandelt.

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<Vec<i32>> dereferenziert (über DerefMut) zu Vec<i32>. Du kannst guard.push(...) direkt aufrufen — kein expliziter * nötig.

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 Variante von Deref-Coercion — flexibler bei Cross-Type-Konvertierungen. Klassisch in std::fs-APIs.

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