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.
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
usizeund ein Csize_tsind identisch - Structs sind in beiden Sprachen grundlegend dieselbe Idee
Wozu FFI?
- Bestand bestehender Bibliotheken: SQLite, libgit2, OpenSSL
- System-C-APIs:
libc-Funktionen wiestrlen,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
Haken setzen, um deinen Lernfortschritt zu markieren:
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-Typ | std::ffi-Typ |
|---|---|
short | c_short |
int | c_int |
long | c_long |
long long | c_longlong |
unsigned / unsigned int | c_uint |
unsigned long | c_ulong |
char | c_char |
signed char | c_schar |
unsigned char | c_uchar |
float / double | c_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) undCStr(geliehen) — null-terminierte C-StringsOsString/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
Haken setzen, um deinen Lernfortschritt zu markieren:
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
CString::new(str)erzeugt einen null-terminierten Besitz-String..as_ptr()liefert den*const c_char-Zeiger darauf.- Ü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?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
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
Haken setzen, um deinen Lernfortschritt zu markieren:
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:
- Kontrakt sicherstellen: gültige, null-terminierte Strings; gültige Zeiger
unsafeauf das Innere beschränken und jedes Vorkommen mit// SAFETY:begründen- C-Fehler abfangen:
git_libgit2_init()liefert einenc_int-Status — deine Hülle wandelt ihn in einResultoderpanic!um - Sauber deinitialisieren:
Dropverwendet, 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?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren:
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 SQLitelibc— 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.
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
Haken setzen, um deinen Lernfortschritt zu markieren:
Ü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:
- Deklariere
strlen(s: *const c_char) -> usizein einemunsafe extern "C"Block (importierec_charausstd::ffi). - Schreibe eine sichere Funktion
textlaenge(text: &str) -> usize, die innenCString::new+unsafe { strlen(...) }verwendet, mit// SAFETY:Kommentar. - Teste sie mit einem
&str-Argument — ohne dassmainselbstunsafeberührt. - Ü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, innenunsafe { ... }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?
✅ Wichtige Erkenntnisse
Haken setzen, um deinen Lernfortschritt zu markieren: