programowanieProgramista systemowy

Wyjaśnij, jak działa system FFI (Foreign Function Interface) w Rust. Jakie są wymagania i pułapki związane z bezpiecznym wywoływaniem funkcji z C?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

FFI w Rust pozwala na wywoływanie funkcji zadeklarowanych w zewnętrznych bibliotekach (np. C/C++) oraz eksportowanie funkcji Rust na zewnątrz. W tym celu używa się słowa kluczowego extern. Wymagania:

  • Wszystkie wywołania FFI muszą być owinięte w deklarację z unsafe;
  • ABI (Application Binary Interface) musi być zgodny (zwykle "C");
  • Typy danych muszą być zgodne — ważne jest używanie prymitywów o stałych rozmiarach (i32, u64 itp.);
  • Zarządzanie pamięcią — należy bardzo uważnie podchodzić do posiadania, wycieków i podwójnego zwolnienia pamięci.

Przykład opakowania:

extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("{}", abs(-5)); } }

Eksportowanie funkcji z Rust do użycia w C można zrobić w ten sposób:

#[no_mangle] pub extern "C" fn sum(a: i32, b: i32) -> i32 { a + b }

Pytanie z podstępem

Pytanie: Czy Rust gwarantuje, że jeśli owiniesz wywołanie funkcji C w unsafe, wszystko będzie działać poprawnie pod względem bezpieczeństwa w wątkach i wychwytywania UB?

Odpowiedź: Nie! unsafe to obietnica dla kompilatora, że sam odpowiadasz za spełnienie wszystkich wymagań bezpieczeństwa (aliasing, bezpieczeństwo wątków, dostęp do pamięci). Rust nie przeprowadza kontroli kodu wewnątrz C. Na przykład, race condition lub UB w kodzie biblioteki może "zepsuć" runtime programów Rust. Nawet jeśli kod Rust jest kompilowany, rzeczywiste wykonanie może doprowadzić do awarii.


Historia

W finansowym dostawcy danych zespół zintegrował bibliotekę C poprzez FFI, nie sprawdzając rozmiarów typów. Na x86_64 oczekiwano, że long i i64 są zgodne, lecz okazało się, że rozmiary były różne na innych platformach — niewłaściwe odczytywanie pamięci doprowadziło do błędów w obliczeniach.

Historia

Programista stworzył opakowanie dla API C, zapominając o przekazywanych wskaźnikach na bufor. Ponieważ Rust automatycznie zwalniał pamięć, funkcja C czasami działała na już zwolnionej, co prowadziło do awarii i błędów.

Historia

W produkcie klient-serwerowym moduł Rust odwoływał się do biblioteki C, nie uwzględniając bezpieczeństwa wielowątkowego. Biblioteka nie była thread-safe, a programy Rust odwoływały się do niej z różnych wątków jednocześnie, co prowadziło do awarii i uszkodzenia danych.