Rust
Linee guida non sicure
Ricerca…
introduzione
Spiega perché certe cose sono marcate unsafe
in Rust e perché potremmo aver bisogno di usare questo portello di fuga in alcune (rare) situazioni.
Data Races
Le razze di dati si verificano quando un pezzo di memoria viene aggiornato da una parte mentre un altro tenta di leggerlo o aggiornarlo contemporaneamente (senza sincronizzazione tra i due). Diamo un'occhiata al classico esempio di una corsa di dati usando un contatore condiviso.
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::thread;
// `UnsafeCell` is a zero-cost wrapper which informs the compiler that "what it
// contains might be shared mutably." This is used only for static analysis, and
// gets optimized away in release builds.
struct RacyUsize(UnsafeCell<usize>);
// Since UnsafeCell is not thread-safe, the compiler will not auto-impl Sync for
// any type containig it. And manually impl-ing Sync is "unsafe".
unsafe impl Sync for RacyUsize {}
impl RacyUsize {
fn new(v: usize) -> RacyUsize {
RacyUsize(UnsafeCell::new(v))
}
fn get(&self) -> usize {
// UnsafeCell::get() returns a raw pointer to the value it contains
// Dereferencing a raw pointer is also "unsafe"
unsafe { *self.0.get() }
}
fn set(&self, v: usize) { // note: `&self` and not `&mut self`
unsafe { *self.0.get() = v }
}
}
fn main() {
let racy_num = Arc::new(RacyUsize::new(0));
let mut handlers = vec![];
for _ in 0..10 {
let racy_num = racy_num.clone();
handlers.push(thread::spawn(move || {
for i in 0..1000 {
if i % 200 == 0 {
// give up the time slice to scheduler
thread::yield_now();
// this is needed to interleave the threads so as to observe
// data race, otherwise the threads will most likely be
// scheduled one after another.
}
// increment by one
racy_num.set(racy_num.get() + 1);
}
}));
}
for th in handlers {
th.join().unwrap();
}
println!("{}", racy_num.get());
}
L'uscita sarà quasi sempre inferiore a 10000
(10 thread × 1000) quando viene eseguita su un processore multi-core.
In questo esempio, una corsa di dati ha prodotto un valore logicamente sbagliato ma comunque significativo. Questo perché solo una singola parola è stata coinvolta nella gara e quindi un aggiornamento non può averlo parzialmente modificato. Ma le gare di dati in generale possono produrre valori corrotti che non sono validi per un tipo (tipo non sicuro) quando l'oggetto che si corre racchiude più parole e / o producono valori che puntano a posizioni di memoria non valide (memoria non sicura) quando sono coinvolti i puntatori.
Tuttavia, l'uso attento dei primitivi atomici può consentire la costruzione di strutture di dati molto efficienti che possono internamente eseguire alcune di queste operazioni "non sicure" per eseguire azioni che non sono statisticamente verificabili dal sistema di tipi di Rust, ma corrette nel loro insieme (cioè per costruire una cassaforte astrazione).