Unsafe Cell
Lecture 3 min. âą
Bonjour Ă toutes et Ă tous đ
Nous nâavons pas trop fait de Rust unsafe sur ce blog, il est temps de remĂ©dier Ă ce manque.
Ne vous inquiĂ©tez pas, on ne va pas faire du trop complexe. Jâai juste besoin dâune brique fondamentale de lâunsafe que lâon nomme une Unsafe Cell.
Voici la structure:
#[repr(transparent)]
struct UnsafeCell<T: ?Sized> {
value : T
}Deux choses Ă remarquer:
T: ?Sized: permet de stocker ce que lâon veut au sein devalue#[repr(transparent)]: Une structure de 1 seule champ peut avoir la mĂȘme reprĂ©sentation en mĂ©moire que le type du champ
Les deux cumulĂ©s permettent dâavoir un type totalement transparent qui accueille nâimporte quoi sans broncher.
Comme dâhabitude la pratique vaut mieux que les long discours.
On construit notre UnsafeCell.
fn main() {
let cell = UnsafeCell::new(0);
}Si on essaye de dbg!, on obtient un résultat des plus étrange:
dbg!(cell); // cell = UnsafeCell { .. }Bon on aura pas grand chose de cette maniĂšre.
LâAPI nous fourni dâautres mĂ©thodes.
fn main() {
let cell = UnsafeCell::new(0);
dbg!(cell.get()) // 0x000000e33c58f56c
}Ah câest pas ce que jâespĂ©rai, ça ressemble plus Ă une adresse,
Et pour cause câest bien une adresse, celle de la UnsafeCell et donc par extension de la donnĂ©e.
La signature de la méthode UnsafeCell::get est
fn get(&self) -> *mut T;Le type *mut T, on appelle ça un raw pointer, il existe deux types de raw pointer:
*const T: les donnĂ©es qui sont derriĂšres ne bougeront pas, ou en tout cas elle ne sont pas sensĂ©es bouger*mut T: les donnĂ©es pointĂ©es nâont aucune garantie de ne pas ĂȘtre modifiĂ©es
En Rust pour déréférencer, on utilise la syntaxe *x.
Cette syntaxe est également valide pour les raw pointers.
fn main() {
let cell = UnsafeCell::new(0);
dbg!(*cell.get()) // Erreur
}Félicitation, vous avez mis en rogne le compilateur:
error: dereference of raw pointer is unsafe and requires unsafe function or block
|
| dbg!(*cell.get());
| ^^^^^^^^^^^ dereference of raw pointer
|
= note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behaviorPour le paraphrasĂ©, Rust ne fourni aucune garantie sur lâexistence ou la cohĂ©rence des donnĂ©es Ă cette adresse mĂ©moire et donc ne vous laisse pas y accĂ©der.
Pour ĂȘtre en capacitĂ© de le faire, il va falloir dĂ©brayĂ© Rust.
Pour cela, nous allons utiliser une syntaxe qui permet de rendre un bloc de code oĂč le dev devient responsable de ses Ăąneries, en gros on signe une dĂ©charge de responsabilitĂ©.
Et Rust se lave les mains de ce que vous allez y faire. đ
Attention
unsafene libĂšre pas du Borrow Checker.unsafe { let mut a = 12; let ref_a_mut = &mut a; let ref_a = &a; dbg!(ref_a_mut); }error: cannot borrow `a` as immutable because it is also borrowed as mutable let ref_a_mut = &mut a; ------ mutable borrow occurs here let ref_a = &a; ^^ immutable borrow occurs here dbg!(ref_a_mut); --------- mutable borrow later used hereCette notion va ĂȘtre cruciale pour implĂ©menter la sĂ©curitĂ© du langage tout en permettant de faire des choses qui sont nĂ©cessaires.
Bien, entourons dâun bloc unsafe.
fn main() {
let cell = UnsafeCell::new(0);
unsafe {
dbg!(*cell.get()); // 0
}
}Parfait, on accĂšde Ă la donnĂ©e. đ
Le retour de UnsafeCell::get Ă©tant un *mut T, nous avons alors la possibilitĂ© de lire et dâĂ©crire les donnĂ©es.
Et ĂȘtre les seuls Ă le faire grĂące aux garanties du BorrowChecker car on a besoin dâune rĂ©fĂ©rence non-mutable de &self.
Donc personne nâest censĂ© muter les donnĂ©es en âmĂȘme tempsâ que nous.
Nous pouvons alors muter, donc incrémenter notre donnée.
let cell = UnsafeCell::new(0);
unsafe {
*cell.get() += 1;
dbg!(*cell.get()); // 1
}Sympa, non ? đ
Mais alors, en pratique, à quoi ça sert ?
Voyez le code suivant:
// On déclare une structure qui référence une UnsafeCell
struct Data<'a> {
shared: &'a UnsafeCell<i32>
}
impl Data<'_> {
// Cette méthode incrémente de 1 le contenu de la UnsafeCell
fn inc(&self) {
unsafe {
*self.shared.get() += 1
}
}
}
fn main() {
let shared = UnsafeCell::new(0);
// On créé deux instances
let data1 = Data {shared: &shared};
let data2 = Data {shared: &shared};
// On incrémente depuis les deux instances
data1.inc();
data2.inc();
// On consomme la UnsafeCell
dbg!(shared.into_inner()); // 2
}Les UnsafeCell sont un moyen de muter une référence non mutable tout en garantissant que les rÚgles du Borrow Checker sont respectées.
Elles vont ĂȘtre extrĂȘmement utiles pour tout un pan de Rust.
Un dernier truc en passant.
Il est également possible de réaliser des mutation sur la UnsafeCell si vous possédez une version mutable de celle-ci.
let mut cell = UnsafeCell::new(0);
*cell.get_mut() += 1;
dbg!(cell.into_inner()); // 1Dans ce mode safe, vous avez toutes les garanties de Rust sur lâexistence et la validitĂ© des donnĂ©es, ainsi que lâexclusivitĂ© de la mutabilitĂ©.
VoilĂ , câĂ©tait un pas timide de ce blog dans le chaos organisĂ© de lâunsafe de Rust! đ
PS: Jâai rĂ©alisĂ© une correction de cette article ici. Merci Ă leur vigilence.
