Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kapitel 19: Unsafe Rust und FFI – Der Klettergurt und der freie Fall

Stell dir vor, du gehst mit einem erfahrenen Kletterpartner in den Bergen wandern. Du trägst einen Klettergurt und bist mit einem elastischen Seil an deinen Partner gekoppelt.

Dein Kletterpartner passt unaufhörlich auf dich auf (in Rust ist das der Borrow Checker). Jedes Mal, wenn du abrutschst, fängt dich das Seil ab. Du kannst zwar stolpern, aber du stürzt niemals in den Abgrund. Das ist die normale, sichere Welt von Rust.

Nun kommt ihr an eine Felswand, an der eine wichtige Schraube locker ist, die du festziehen musst. Die Schraube liegt jedoch auf einem extrem schmalen Felsvorsprung, den man mit Seilsicherung nicht erreichen kann.

Du sagst zu deinem Partner: „Lass mich kurz los. Ich hänge mich aus dem Seil aus (in Rust: ein unsafe-Block). Ich passe selbst ganz genau auf meine Schritte auf und übernehme die Verantwortung.“

Das bedeutet nicht, dass du sofort abstürzt, sobald du das Seil löst. Wenn du trittsicher bist und dich konzentrierst, ziehst du die Schraube fest und kehrst unbeschadet zurück. Aber wenn du jetzt einen falschen Schritt machst, gibt es kein Seil mehr, das dich auffängt. Du stürzt ungebremst ab.

In der Programmierung ist das ähnlich. Normalerweise schützt dich Rust vor jedem Speicherfehler. Doch wenn du direkt mit der Computer-Hardware sprechen, Betriebssysteme programmieren oder alten C-Code einbinden willst, musst du das Seil kurz lösen. Das machen wir mit dem Schlüsselwort unsafe.


1. Lernziele – Das wirst du heute lernen

  • Was unsafe bedeutet: Du verstehst, warum es unsicheren Code geben muss und wie er uns nützt.
  • Die Ausbruchssyntax nutzen: Du lernst, Code in unsafe { ... } Blöcke einzuschließen.
  • Rohe Zeiger (Raw Pointers) verstehen: Du erfährst, wie Zeiger direkt auf Speicheradressen zeigen.
  • Zeiger erzeugen und nutzen: Du erstellst rohe Zeiger und greifst über sie auf Daten zu.
  • Typische Compilerfehler: Du lernst, warum der nackte Zugriff auf Adressen ohne unsafe verboten ist.

2. Was ist unsafe und was schaltet es ab?

Ein häufiges Missverständnis: Das Wort unsafe schaltet den Borrow Checker nicht ab. Der Compiler prüft weiterhin Typen, Referenzen und Lebenszeiten im gesamten Programm.

unsafe ist lediglich eine Eintrittskarte zu fünf Superkräften, die im normalen Rust streng verboten sind:

  1. Rohe Zeiger dereferenzieren (auf direkte Speicheradressen zugreifen).
  2. Unsichere Funktionen oder Methoden aufrufen.
  3. Unsichere Traits implementieren.
  4. Globale, veränderliche Variablen (static mut) lesen oder verändern.
  5. Auf die Felder einer union zugreifen.

3. Rohe Zeiger (Raw Pointers): Adressen auf der Festplatte des RAMs

Bisher hast du in Rust mit sicheren Referenzen gearbeitet (&T und &mut T). Rohe Zeiger sind die systemnahe Variante davon. Sie entsprechen den Zeigern in C oder C++.

Es gibt zwei Arten von rohen Zeigern:

  • *const T: Ein unveränderlicher roher Zeiger auf einen Wert vom Typ T.
  • *mut T: Ein veränderlicher roher Zeiger auf einen Wert vom Typ T.

Was unterscheidet rohe Zeiger von normalen Referenzen?

  1. Sie dürfen den Wert Null haben (auf die Adresse 0 zeigen, also ins Nichts).
  2. Sie dürfen gleichzeitig als Leser und Schreiber auf dieselbe Adresse zeigen (keine Aliasing-Regeln).
  3. Der Compiler garantiert nicht, ob das Objekt an der Adresse überhaupt noch existiert (keine Lebenszeit-Garantie).

Wie erstellen und nutzen wir sie?

Das Erstellen eines Zeigers ist völlig sicher und erfordert kein unsafe. Erst das Dereferenzieren (das Auslesen oder Ändern des Werts an der Adresse) ist gefährlich und erfordert einen unsafe-Block:

fn main() {
    let mut zahl = 42;

    // Wir erstellen rohe Zeiger aus normalen Referenzen mittels 'as'
    // Das Erstellen ist völlig sicher!
    let zeiger_konstant: *const i32 = &zahl as *const i32;
    let zeiger_veraenderlich: *mut i32 = &mut zahl as *mut i32;

    // Die Speicheradresse selbst ausgeben (sicher):
    println!("Speicheradresse: {:?}", zeiger_konstant);

    // Der Zugriff auf den WERT an der Adresse erfordert einen unsafe-Block!
    unsafe {
        // Den Wert lesen
        println!("Wert über Zeiger: {}", *zeiger_konstant);

        // Den Wert über den veränderlichen Zeiger überschreiben
        *zeiger_veraenderlich = 100;

        println!("Geänderter Wert: {}", *zeiger_konstant);
    }
}

4. Compilerfehler-Show: Dereferenzierung ohne unsafe

Was passiert, wenn du vergisst, den Zugriff auf den Zeiger in einen unsafe-Block zu wickeln?

fn main() {
    let x = 10;
    let zeiger = &x as *const i32;

    // Wir versuchen, den Zeiger direkt auszulesen:
    let wert = *zeiger; // Compilerfehler!
    println!("{}", wert);
}

Die Fehlermeldung des Compilers:

error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
 --> src/main.rs:6:16
  |
6 |     let wert = *zeiger;
  |                ^^^^^^^ dereference of raw pointer
  |
  = note: raw pointers may be null, dangling, or misaligned; they can violate aliasing rules and cause data races

Die Erklärung:

Der Compiler warnt dich eindringlich: Der Zeiger könnte auf eine ungültige Adresse zeigen, null sein oder schlecht ausgerichtet sein. Wenn du darauf zugreifst, riskierst du einen Programmabsturz.

Die Lösung: Wickele den Zugriff in einen unsafe { ... } Block, nachdem du sichergestellt hast, dass der Zeiger gültig ist:

#![allow(unused)]
fn main() {
unsafe {
    let wert = *zeiger;
}
}

5. Zusammenfassung

  1. unsafe kennzeichnet Bereiche, in denen der Entwickler selbst für die Speichersicherheit haftet.
  2. Das Erstellen von rohen Zeigern (*const T / *mut T) ist sicher.
  3. Das Dereferenzieren (Lesen/Schreiben) von rohen Zeigern erfordert zwingend einen unsafe-Block.
  4. Rohe Zeiger dürfen null sein und besitzen keine Lebenszeitgarantien des Compilers.