Fette Zeiger in Rust: Fat Pointer verstehen

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Was verbirgt sich hinter dem "fetten" Zeiger? Fat Pointer in Rust erklärt

\n\n

Stellen Sie sich vor, Sie gehen in eine Bibliothek und fragen nach einem Buch. Der Bibliothekar gibt Ihnen einen kleinen Zettel mit der Regal- und Fachnummer – das ist ein normaler Zeiger. Aber was, wenn Sie nicht wissen, wie groß das Buch ist? Oder wenn es sich plötzlich um einen dreibändigen Roman handelt? Sie bräuchten einen zweiten Zettel: "Band 1 von 3" oder "Länge: 450 Seiten".

\n\n

In Rust nennt man einen solchen "doppelten" Zeiger Fat Pointer (fetter Zeiger). Das ist keine Magie, sondern einfach ein Zahlenpaar: die Adresse der Daten und deren Metainformationen (meistens die Länge oder ein Zeiger auf eine virtuelle Methodentabelle). Lassen Sie uns anhand von echten Codebeispielen verstehen, warum das nötig ist und wie es funktioniert.

\n\n

Normale Zeiger: dünn und schnell

\n\n

Beginnen wir mit den Grundlagen. In Rust gibt es einfache Zeiger – &T (Referenz) oder *const T (roher Zeiger). Sie belegen genau 8 Byte (auf einem 64-Bit-System) und speichern nur die Speicheradresse.

\n\n
fn main() {\n    let x: i32 = 42;\n    let ptr: &i32 = &x; // dünner Zeiger – 8 Byte\n    println!("Größe der Referenz: {} Byte", std::mem::size_of_val(&ptr));\n}\n
\n\n

Ausgabe: Größe der Referenz: 8 Byte. Alles vorhersehbar: Der Compiler weiß, dass i32 4 Byte groß ist, daher sind keine zusätzlichen Informationen nötig. Aber was, wenn die Datengröße im Voraus unbekannt ist?

\n\n

Erster Fat Pointer: Slices (&[T])

\n\n

Ein Slice ist ein "Fenster" in ein Array oder einen Vektor. Um es nutzen zu können, muss man jedoch nicht nur den Anfang, sondern auch die Anzahl der Elemente kennen. Rust speichert dies als Paar (ptr, len) – das ist ein Fat Pointer.

\n\n
fn main() {\n    let arr = [1, 2, 3, 4, 5];\n    let slice: &[i32] = &arr[1..4]; // Slice aus 3 Elementen\n\n    println!("Größe des Slices: {} Byte", std::mem::size_of_val(&slice));\n    println!("Elemente: {:?}", slice);\n}\n
\n\n

Ausgabe: Größe des Slices: 16 Byte (8 Byte Zeiger + 8 Byte Länge). Wenn wir nur die Adresse übergeben würden, wüssten wir nicht, wo wir aufhören müssen. Deshalb ist ein Slice ein klassisches Beispiel für einen Fat Pointer.

\n\n

Übrigens funktioniert der String &str genauso: Es ist ein Fat Pointer (*const u8, usize). Deshalb belegt &str 16 Byte, während &String nur 8 Byte belegt (weil String die Länge selbst speichert).

\n\n

Zweiter Fat Pointer: Trait-Objekte (&dyn Trait)

\n\n

In Rust gibt es dynamische Dispatch über Trait-Objekte. Wenn Sie &dyn MyTrait schreiben, weiß der Compiler nicht, welcher konkrete Typ übergeben wird. Er benötigt eine virtuelle Methodentabelle (vtable), um die richtigen Funktionen aufzurufen.

\n\n
trait Animal {\n    fn speak(&self);\n}\n\nstruct Dog;\nstruct Cat;\n\nimpl Animal for Dog {\n    fn speak(&self) { println!("Wuff!"); }\n}\n\nimpl Animal for Cat {\n    fn speak(&self) { println!("Miau!"); }\n}\n\nfn main() {\n    let dog = Dog;\n    let cat = Cat;\n\n    let animals: [&dyn Animal; 2] = [&dog, &cat];\n\n    println!("Größe von &dyn Animal: {} Byte", std::mem::size_of::<&dyn Animal>());\n\n    for animal in &animals {\n        animal.speak();\n    }\n}\n
\n\n

Ausgabe: Größe von &dyn Animal: 16 Byte. Diese 16 Byte sind wieder ein Fat Pointer: Die ersten 8 Byte sind die Datenadresse (Dog oder Cat), die zweiten 8 Byte sind die Adresse der vtable (Methodentabelle).

\n\n

Wie beeinflusst der Fat Pointer die Leistung?

\n\n

Fat Pointer sind ein Kompromiss. Einerseits tragen sie mehr Informationen, was die Arbeit mit dynamischen Typen ermöglicht. Andererseits:

\n\n
    \n
  • Speicher: Ein Fat Pointer belegt 16 Byte statt 8. Das ist nicht kritisch, kann sich aber in großen Sammlungen bemerkbar machen.
  • \n
  • Geschwindigkeit: Der Zugriff über die vtable (bei Trait-Objekten) ist etwas langsamer als ein direkter Methodenaufruf (Monomorphisierung). Der Unterschi

Blogs

Buchempfehlungen