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:
- Dereferenziare puntatori raw
- Chiamare funzioni unsafe
- Accedere a variabili statiche mutabili
- Implementare trait unsafe
- 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
libcper FFI ebytemuckper 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.