Unsafe Cell
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:
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
.
Si on essaye de dbg!
, on obtient un résultat des plus étrange:
dbg!; // cell = UnsafeCell { .. }
Bon on aura pas grand chose de cette manière.
L'API nous fourni d'autres méthodes.
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
;
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.
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 behavior
Pour 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
unsafe
ne libère pas du Borrow Checker.unsafe
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 here
Cette 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
.
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 = new;
unsafe
Sympa, non ? 😊
Mais alors, en pratique, à quoi ça sert ?
Voyez le code suivant:
// On déclare une structure qui référence une UnsafeCell
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 = new;
*cell.get_mut += 1;
dbg!; // 1
Dans 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.
Ce travail est sous licence CC BY-NC-SA 4.0.