Rust
Osäkra riktlinjer
Sök…
Introduktion
Förklara varför vissa saker är märkta som unsafe
i Rust, och varför vi kan behöva använda denna utrymningslucka i vissa (sällsynta) situationer.
Data Races
Dataraser inträffar när ett minne uppdateras av en part medan en annan försöker läsa eller uppdatera det samtidigt (utan synkronisering mellan de två). Låt oss titta på det klassiska exemplet på ett datarace med en delad räknare.
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());
}
Utgången kommer nästan alltid att vara mindre än 10000
(10 trådar × 1000) när den körs på en flerkärnig processor.
I det här exemplet har en datarace producerat ett logiskt fel men ändå meningsfullt värde. Detta beror på att bara ett enda ord var involverat i loppet och därför kunde en uppdatering inte delvis ha ändrat det. Men datalopp i allmänhet kan producera korrupta värden som är ogiltiga för en typ (typ osäkra) när objektet som rasas för spänner över flera ord och / eller producera värden som pekar på ogiltiga minnesplatser (minne osäkra) när pekare är involverade.
En noggrann användning av atomprimitiv kan emellertid möjliggöra konstruktion av mycket effektiva datastrukturer som internt kan behöva utföra några av dessa "osäkra" operationer för att utföra åtgärder som inte är statiskt verifierbara med Rusts typsystem, men överhuvudtaget korrekt (dvs för att bygga en säker abstraktion).