00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Unsafe in Rust

Il blocco unsafe in Rust permette di eseguire operazioni che il compilatore non può verificare come sicure. Unsafe non disattiva il borrow checker: sblocca solo specifiche capacità aggiuntive.

Cosa Permette Unsafe

Ci sono 5 operazioni possibili solo in un blocco unsafe:

  1. Dereferenziare puntatori raw
  2. Chiamare funzioni unsafe
  3. Accedere a variabili statiche mutabili
  4. Implementare trait unsafe
  5. Accedere a campi di union

Puntatori Raw

fn main() {
    let mut valore = 42;

    // Creare puntatori raw è sicuro
    let ptr_imm: *const i32 = &valore;
    let ptr_mut: *mut i32 = &mut valore;

    // Dereferenziarli richiede unsafe
    unsafe {
        println!("Valore: {}", *ptr_imm);
        *ptr_mut = 100;
        println!("Nuovo valore: {}", *ptr_mut);
    }
}

Funzioni Unsafe

unsafe fn operazione_pericolosa() -> i32 {
    // Codice che il compilatore non può verificare
    42
}

fn main() {
    let risultato = unsafe { operazione_pericolosa() };
    println!("{}", risultato);
}

Variabili Statiche Mutabili

static mut CONTATORE: u32 = 0;

fn incrementa() {
    unsafe {
        CONTATORE += 1;
    }
}

fn leggi() -> u32 {
    unsafe { CONTATORE }
}

fn main() {
    incrementa();
    incrementa();
    println!("Contatore: {}", leggi()); // 2
}

Attenzione: Le variabili statiche mutabili sono intrinsecamente non thread-safe. Preferisci AtomicU32 o Mutex.

Trait Unsafe

unsafe trait MioTraitUnsafe {
    fn operazione(&self);
}

unsafe impl MioTraitUnsafe for i32 {
    fn operazione(&self) {
        println!("Valore: {}", self);
    }
}

I trait Send e Sync sono esempi di trait unsafe nella libreria standard.

FFI (Foreign Function Interface)

Interfacciarsi con codice C richiede unsafe:

extern "C" {
    fn abs(input: i32) -> i32;
    fn strlen(s: *const u8) -> usize;
}

fn main() {
    let risultato = unsafe { abs(-42) };
    println!("Valore assoluto: {}", risultato); // 42
}

// Esporre funzioni Rust per C
#[no_mangle]
pub extern "C" fn somma_rust(a: i32, b: i32) -> i32 {
    a + b
}

Astrazioni Sicure su Unsafe

Il pattern corretto è avvolgere il codice unsafe in un’API sicura:

fn dividi_slice(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    assert!(mid <= slice.len());

    let ptr = slice.as_mut_ptr();
    let len = slice.len();

    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut dati = vec![1, 2, 3, 4, 5];
    let (prima, seconda) = dividi_slice(&mut dati, 3);
    println!("{:?} {:?}", prima, seconda); // [1, 2, 3] [4, 5]
}

Linee Guida

  • Minimizza la quantità di codice dentro unsafe
  • Documenta le invarianti che il codice unsafe assume
  • Avvolgi sempre unsafe in API sicure
  • Usa unsafe solo quando non c’è alternativa sicura
  • Considera crate come libc per FFI e bytemuck per reinterpretazione di byte

Conclusione

unsafe non significa “codice pericoloso” ma “codice la cui sicurezza è responsabilità del programmatore”. Il compilatore continua a controllare tutto il resto. Usa unsafe solo quando necessario, minimizza il suo scope e avvolgilo sempre in astrazioni sicure. La maggior parte del codice Rust non richiede mai unsafe.