extern (funcții C și FFI) în Rust

Cod unsafe, FFI și async Rust 1.0 (unsafe extern și safe fn: 1.82, obligatoriu în ediția 2024)

extern leagă Rust de cod scris în alte limbaje: declară funcții C de apelat din Rust și funcții Rust cu convenția de apel C.

Sintaxă
unsafe extern "C" {
    fn functie_c(x: i32) -> i32;        // apelul cere unsafe
    safe fn sigura(x: i32) -> i32;      // marcată sigură de tine
}
extern "C" fn pentru_c(x: i32) -> i32 { ... }   // funcție Rust apelabilă din C
extern crate nume;                       // stil vechi (ediția 2015)

Ce face extern (funcții C și FFI)

Un bloc unsafe extern "C" { ... } declară funcții dintr-o bibliotecă C (FFI, Foreign Function Interface). Rust nu poate verifica ce fac acele funcții, deci apelul lor cere unsafe. În ediția 2024 blocul trebuie scris unsafe extern, iar o funcție pe care o știi sigură poate fi declarată safe fn, ca să fie apelată fără unsafe. Biblioteca C standard este legată automat; pentru altele se folosește #[link(name = "...")] sau Cargo.

Tipurile trebuie să corespundă celor din C: i32 pentru int (sau std::ffi::c_int), *const c_char pentru const char*, usize pentru size_t. Textele C se termină cu octetul 0; în Rust le scrii cu literalul c"text" (tipul &CStr) sau cu CString.

extern "C" fn definește o funcție Rust care folosește convenția de apel C, ca să poată fi dată unei biblioteci C (de exemplu o funcție de comparare pentru qsort). Cu #[unsafe(no_mangle)], numele ei rămâne neschimbat și poate fi apelată din C. extern crate este forma veche (ediția 2015) de a declara un crate dependent; azi Cargo face asta automat.

Exemple

Exemplul 1: Apelul unor funcții din biblioteca C

strlen primește un pointer la un text C, deci apelul cere unsafe. abs este declarată safe fn, deci se apelează direct. Literalul c"Iași" are octetul 0 la final; strlen numără octeții (ș are 2).

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

unsafe extern "C" {
    fn strlen(s: *const c_char) -> usize;
    safe fn abs(x: c_int) -> c_int;
}

fn main() {
    let oras = c"Iași";
    // SAFETY: `oras` este un text C valid, terminat cu 0.
    let lungime = unsafe { strlen(oras.as_ptr()) };
    println!("strlen(\"Iași\") = {lungime} octeți");
    println!("abs(-42) = {}", abs(-42));
}

Rezultat

strlen("Iași") = 5 octeți
abs(-42) = 42

Exemplul 2: O funcție Rust dată unei funcții C: qsort

qsort din C sortează orice tablou, dacă primește o funcție de comparare. compara este scrisă în Rust cu extern "C", deci C o poate apela. (În cod real ai folosi sort(); exemplul arată doar mecanismul.)

use std::ffi::{c_int, c_void};

unsafe extern "C" {
    fn qsort(
        baza: *mut c_void,
        numar: usize,
        marime: usize,
        compara: extern "C" fn(*const c_void, *const c_void) -> c_int,
    );
}

extern "C" fn compara(a: *const c_void, b: *const c_void) -> c_int {
    // SAFETY: qsort ne dă pointeri la elemente din tabloul nostru de i32.
    let (x, y) = unsafe { (*(a as *const i32), *(b as *const i32)) };
    x.cmp(&y) as c_int
}

fn main() {
    let mut temperaturi = [21, -3, 18, 7, 0, 25];
    // SAFETY: pointerul, numărul și mărimea elementelor descriu exact tabloul.
    unsafe {
        qsort(
            temperaturi.as_mut_ptr() as *mut c_void,
            temperaturi.len(),
            size_of::<i32>(),
            compara,
        );
    }
    println!("{temperaturi:?}");
}

Rezultat

[-3, 0, 7, 18, 21, 25]

Greșeli frecvente

  • Scrii extern "C" { ... } fără unsafe în ediția 2024: eroare extern blocks must be unsafe.
  • Dai unei funcții C un &str sau un String (s.as_ptr()). Textele Rust nu se termină cu 0, deci C citește dincolo de ele. Folosește CString::new(text) sau un literal c"...".
  • Declari greșit semnătura (de exemplu i64 în loc de int). Compilatorul nu poate verifica declarațiile extern; o semnătură greșită dă comportament nedefinit la rulare.
  • Lași o panică să iasă dintr-o funcție extern "C" apelată de C. Programul este oprit (abort); prinde erorile în Rust și returnează un cod de eroare.

Comenzi înrudite

  • unsafe și pointeri bruți: unsafe permite cinci operații pe care compilatorul nu le poate verifica, ca dereferențierea pointerilor bruți; corectitudinea o garantezi tu.
  • fn: Declară o funcție. Tipul fiecărui parametru este obligatoriu, iar ultima expresie fără punct și virgulă este valoarea returnată.
  • atribute (#[derive], #[cfg], #[allow]): Informații pentru compilator scrise cu #[…] deasupra unui element: generează cod (derive), aleg codul compilat (cfg) sau schimbă avertismentele.
  • cargo add: Adaugă o dependență (un crate de pe crates.io) în Cargo.toml, cu ultima versiune compatibilă. cargo remove o scoate.