In Rust wordt de veiligheid bij het werken met threads gegarandeerd door twee automatische traits: Send en Sync.
Send staat toe dat een type tussen threads wordt overgedragen (door eigendomsoverdracht).Sync garandeert dat een type veilig uit meerdere threads tegelijkertijd kan worden gebruikt (via referenties).De meeste standaardtypes in Rust implementeren deze traits standaard. Bijvoorbeeld Arc<T>, Mutex<T> — Send en Sync (als T ook aan deze traits voldoet).
Je kunt deze traits expliciet verbieden of implementeren voor je eigen types. Bijvoorbeeld, als je een intern onveilig veld hebt (zoals een rauwe pointer of externe bronnen), moet je de types !Send of !Sync maken:
use std::marker::PhantomData; use std::rc::Rc; struct MyType { niet_thread_veilig: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> implementeert geen Send/Sync, dus MyType zal deze traits ook niet implementeren.
Als je een low-level wrapper implementeert en handmatig de threadveiligheid beheert, kun je Send/Sync handmatig implementeren (unsafe):
unsafe impl Send for MyType {} unsafe impl Sync for MyType {}
Het is de verantwoordelijkheid van de programmeur om garanties over de threadveiligheid te geven.
Wat gebeurt er als je Rc<T> in meerdere threads via Arc<Rc<T>> haalt?
Vaak denken mensen dat Arc alles beschermt. Maar Rc<T> implanteert niet Send/Sync, zelfs niet als het in Arc is verpakt! Zo:
use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // De compiler laat dit niet toe! }
Arc compenseert niet voor het ontbreken van Send/Sync voor ingesloten leden.
Verhaal
In een project was er een poging om Arc<Rc<T>> te gebruiken om gegevens tussen threads te delen en eigenaarsschap niet-blokkerend te delen. De applicatie crashte tijdens uitvoering met onvoorspelbaar gedrag; het bleek dat Rc niet threadveilig was, en de kennis over de Send/Sync traits ontbrak aanvankelijk.
Verhaal
In een zelfgemaakte event loop bewaarde het type State een rauwe pointer naar gegevens. Het type State was gemarkeerd als unsafe impl Send, maar synchronisatie was vergeten. Dit resulteerde in een klassieke data race, die pas na de release werd ontdekt.
Verhaal
Een ontwikkelaar implementeerde een newtype wrapper om Mutex, maar maakte deze ten onrechte !Sync, zonder Sync handmatig te implementeren. Dit verhinderde het gebruik van het type in een multithreaded context (bijvoorbeeld binnen Arc<Mutex<T>>), omdat de compiler Sync vereiste. Dit werd opgelost door unsafe impl Sync te implementeren en veiligheidsanalyse uit te voeren.