Skip to content

Modul 21 — Foreign Functions: Rust & C zusammenbringen (FFI)

extern-Blöcke, std::ffi-Typen, CString, #[link] und die libgit2-Anbindung: Wie Rust mit C-Bibliotheken spricht. Lerne, ausländische Funktionen zu deklarieren, C-Typen korrekt abzubilden und eine sichere Hülle um rohe FFI zu bauen.

Fortschritt0/7 Lektionen
102

Was ist FFI? Der gemeinsame Nenner

Lektion 102 von 7

Rust & C: Maschinensprache verbindet

FFI (Foreign Function Interface) erlaubt es Rust, Funktionen aus anderen Sprachen aufzurufen — meist C/C++. Der gemeinsame Nenner von Rust und C ist Maschinensprache: Wenn du wissen willst, wie Rust-Werte für C aussehen (und umgekehrt), musst du ihre Darstellung im Speicher betrachten.

Die gute Nachricht: Rust und C teilen viele Repräsentationen:

  • ein Rust usize und ein C size_t sind identisch
  • Structs sind in beiden Sprachen grundlegend dieselbe Idee

Wozu FFI?

  • Bestand bestehender Bibliotheken: SQLite, libgit2, OpenSSL
  • System-C-APIs: libc-Funktionen wie strlen, malloc
  • Performance oder Legacy-Code, der sich nicht neu schreiben lässt

C ist bei Repräsentationen lax

C ist überraschend locker bei den Größen seiner Typen: Ein int ist meist 32 Bit, kann aber 16 oder mehr sein; ein char kann signed oder unsigned sein. Um diese Varianz zu bewältigen, bietet std::ffi Rust-Typen, die garantiert dieselbe Darstellung wie bestimmte C-Typen haben — c_int, c_char, c_long und so weiter.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

103

C-Typen abbilden: std::ffi

Lektion 103 von 7

Die std::ffi-Typen

std::ffi definiert Rust-Typen, die garantiert dieselbe Darstellung wie C-Typen haben. Das schützt dich vor plattformabhängigen C-Größen:

C-Typstd::ffi-Typ
shortc_short
intc_int
longc_long
long longc_longlong
unsigned / unsigned intc_uint
unsigned longc_ulong
charc_char
signed charc_schar
unsigned charc_uchar
float / doublec_float / c_double
void*c_void

Warum nicht direkt i32?

Ein C int ist nicht überall garantiert 32 Bit. c_int ist immer das, was C int auf dieser Plattform ist. Damit bleibt dein FFI-Code portabel — und sagt klar, dass du mit C-Code sprichst.

Ausnahme: Strings heißen anders

Neben Zahlen-Typen bekommst du mit std::ffi auch die String-Typen:

  • CString (besitzend) und CStr (geliehen) — null-terminierte C-Strings
  • OsString / OsStr — System-Strings (siehe Modul 18)

Einen c_char-Zeiger als Rust str zu behandeln wäre falsch: C-Strings sind null-terminierte Bytefolgen, nicht garantiert gültiges UTF-8.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

104

extern-Blöcke: C-Funktionen deklarieren

Lektion 104 von 7

Eine C-Funktion anmelden

Ein extern-Block deklariert Funktionen oder Variablen, die in einer anderen Bibliothek definiert sind und mit dem finalen Rust-Programm gelinkt werden. So sagst du Rust, wie C's strlen aussieht:

use std::ffi::c_char;

unsafe extern "C" {
    fn strlen(s: *const c_char) -> usize;
    // ^ C-Konventionen, ^ unsafe, weil sie einen Vertrag hat
}
  • extern "C" = es gelten C-Aufrufkonventionen
  • Die Funktion ist unsafe: sie verlangt z. B. einen validen, null-terminierten String — ein Vertrag, den Rust nicht erzwingen kann.

In Rust aufrufen

use std::ffi::{c_char, CString};

fn main() {
    let rust_str = "I'll be back";
    let null_terminated = CString::new(rust_str).unwrap();

    unsafe {
        assert_eq!(strlen(null_terminated.as_ptr()), 12);
    }
}

CString::new baut einen null-terminierten C-String. Es prüft auf eingebettete Null-Bytes (die in C-Strings unmöglich sind) und fügt das abschließende Null-Byte hinzu. Deshalb das unwrap.

Der Weg String → C

  1. CString::new(str) erzeugt einen null-terminierten Besitz-String.
  2. .as_ptr() liefert den *const c_char-Zeiger darauf.
  3. Übergib ihn an die C-Funktion (innerhalb unsafe).

Eine perfekte Umwandlung eines String (by value) übernimmt den vorhandenen Buffer ohne Kopie; ein &str erfordert eine Heap-Kopie.

Übung: C-Funktion aufrufen

🧩 Übung m21-l3-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

105

Bibliotheken linken: #[link]

Lektion 105 von 7

Eine externe Bibliothek anbinden

Stehen die Funktionen nicht in der Standard-C-Lib, sondern in einer bestimmten Bibliothek, nennst du sie mit #[link] an der Spitze des extern-Blocks. Beispiel libgit2:

use std::ffi::c_int;

#[link(name = "git2")]
unsafe extern "C" {
    pub fn git_libgit2_init() -> c_int;
    pub fn git_libgit2_shutdown() -> c_int;
}

fn main() {
    unsafe {
        git_libgit2_init();
        git_libgit2_shutdown();
    }
}

#[link(name = "git2")] sagt dem Linker: Binde gegen libgit2. Das Verhalten hängt davon ab, wie libgit2 auf deinem System installiert ist.

Deklarieren ist nicht definieren

Der extern-Block gibt Rust Name und Typ der Funktion — die Definition wird später beim Linken bereitgestellt. Genau das ermöglicht es, mit Bibliotheken zu arbeiten, deren Quellcode du gar nicht in Rust neu schreibst: du beschreibst nur die Schnittstelle.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

106

Sichere Hüllen um FFI bauen

Lektion 106 von 7

Das Muster: roh ist innerlich, sicher von außen

Rohe FFI-Calls sind unsafe und mühsam. Die Kunst liegt darin, sie in einem sicheren Interface einzukapseln: Das Innere ist unsafe, die öffentliche API ist safe und bietet einen Vertrag.

struct Rechner;

impl Rechner {
    // SAFETY: libc::strlen ist gültig, weil wir null-terminiert übergeben.
    fn textlaenge(text: &str) -> usize {
        let c = std::ffi::CString::new(text).expect("kein \\0 erlaubt");
        unsafe { strlen(c.as_ptr()) }
    }
}

Von außen ruft man Rechner::textlaenge("hallo") — sicher, ohne unsafe, und mit garantierter korrekter Nutzung.

Der Vertrag von Hüll-Funktionen

Eine sichere Hüll-Funktion muss:

  1. Kontrakt sicherstellen: gültige, null-terminierte Strings; gültige Zeiger
  2. unsafe auf das Innere beschränken und jedes Vorkommen mit // SAFETY: begründen
  3. C-Fehler abfangen: git_libgit2_init() liefert einen c_int-Status — deine Hülle wandelt ihn in ein Result oder panic! um
  4. Sauber deinitialisieren: Drop verwendet, um z. B. git_libgit2_shutdown() automatisch zu rufen

FFI eingrenzen wie Unsafe

Die gleiche Strategie wie in Modul 20: kleine Mengen Unsafe hinter sicheren Interfaces verstecken. So bleibt der Aufrufer-Code vollständig sicher, und alle unangenehmen Verträge (Zeiger, Null-Terminierung, C-Statuscodes) liegen an einer klar markierten Stelle.

Übung: Sichere Hülle für abs

🧩 Übung m21-l5-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

107

Praktische FFI-Projekte: von libgit2 bis crates

Lektion 107 von 7

Real-Welt-FFI

Viele populäre Rust-Crates sind dünne sichere Hüllen um C-Bibliotheken:

  • git2 — sichere Hülle um libgit2 (Git in Rust)
  • openssl / ring — kryptografie-APIs (OpenSSL / C)
  • rusqlite — sichere Hülle um SQLite
  • libc — direkter Zugriff auf die C-Standardbibliothek

Diese Crates funktionieren nach dem Muster von Lektion 5: innen unsafe FFI, außen eine sichere, idiomatische Rust-API.

Das Gesamtbild: raw → safe Schichten

            ┌────────────────────────────────────────┐
            │  Rust-Anwendung (sicher, idiomatisch)  │
            ├────────────────────────────────────────┤
            │  git2 / rusqlite (sichere Hülle)       │
            ├────────────────────────────────────────┤
            │  extern-Blöcke + #[link] (unsafe FFI)  │
            ├────────────────────────────────────────┤
            │  libgit2 / SQLite (C-Bibliothek)       │
            └────────────────────────────────────────┘

Jede Schicht fasst die darunter liegende Unbequemlichkeit ein und bietet nach oben eine bequemere, sicherere Schnittstelle.

FFI-WerkstattBereit
Editor wird geladen…

Automatisierte C-zu-Rust-Übersetzung mit formaler Äquivalenz

Das Werkzeug cozy (2026) nutzt vergleichende symbolische Ausführung, um C-Code automatisch in Rust zu übersetzen — und beweist dabei, dass beide Versionen sich gleich verhalten. Außerhalb der markierten Unterschiede sind die Binaries formal verifiziert äquivalent. Der Entwickler entscheidet für jede Differenz, ob sie beabsichtigt (gut) oder fehlerhaft (schlecht) ist.

Das bedeutet: FFI ist nicht die einzige Brücke zwischen C und Rust. Wo eine vollständige Übersetzung möglich ist, kann formale Verifikation die Sicherheit garantieren, die ein maneller Port nicht liefern kann.

FFI vs. reine Rust-Crates

Nutze FFI, wenn eine ausgereifte C-Bibliothek existiert (SQLite, OpenSSL) — du musst nicht alles neu schreiben. Bevorzuge pure-Rust-Crates, wenn es gleichwertige gibt: Sie sind portabler, brauchen kein System-Linking und teilen Rusts Sicherheits- und Nebeneffekt-Garantien. FFI ist die Brücke, wenn es keine vernünftige reine-Rust-Alternative gibt.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

108

Übung: Eine C-Funktion umhüllen

Lektion 108 von 7

Projekt: strlen sicher einkapseln

Du baust eine sichere Rust-API um die C-Funktion strlen — das prüft die FFI-Kernkonzepte dieses Moduls:

  1. Deklariere strlen(s: *const c_char) -> usize in einem unsafe extern "C" Block (importiere c_char aus std::ffi).
  2. Schreibe eine sichere Funktion textlaenge(text: &str) -> usize, die innen CString::new + unsafe { strlen(...) } verwendet, mit // SAFETY: Kommentar.
  3. Teste sie mit einem &str-Argument — ohne dass main selbst unsafe berührt.
  4. Überlege: Warum ist die öffentliche API sicher, obwohl innen C-Code läuft?

Das verbindet: extern-Blöcke, std::ffi-Typen, CString, Unsafe-Argumentation und das sichere-Hülle-Muster von Lektion 5.

Rezepte aus diesem Modul

  • Deklarieren: unsafe extern "C" { fn strlen(s: *const c_char) -> usize; }
  • C-Typen: std::ffi::{c_char, c_int, c_void, CString}
  • String → C: CString::new(text)? dann .as_ptr()
  • Hülle: sicheres fn, innen unsafe { ... } mit // SAFETY:
  • Linken: #[link(name = "git2")] für Fremdbibliotheken

Praxis-Szenario: Die alte Bibliothek

Die Firma hat eine bewährte C-Bibliothek für Steuerberechnungen. Neu implementieren in Rust? Zu riskant, zu teuer. Aber der neue Webservice entsteht in Rust — und braucht genau diese Berechnungen.

Übung: strlen mit sicherer Hülle

🧩 Übung m21-l7-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

This site uses essential cookies for Stripe payments. No tracking cookies.