Le Rust non sécurisé (unsafe
)
Tout le code Rust que nous avons abordé jusqu'à présent a bénéficié des garanties de sécurité de la mémoire, vérifiées à la compilation. Cependant Rust possède un second langage caché en son sein qui n'applique pas ces vérifications de sécurité de la mémoire : il s'appelle le Rust non sécurisé et fonctionne comme le Rust habituel, mais fournit quelques super-pouvoirs supplémentaires.
Le Rust non sécurisé existe car, par nature, l'analyse statique est conservative. Lorsque le compilateur essaye de déterminer si le code respecte ou non les garanties, il vaut mieux rejeter quelques programmes valides plutôt que d'accepter quelques programmes invalides. Bien que le code puisse être correct, si le compilateur Rust n'a pas assez d'information pour être sûr, il va refuser ce code. Dans ce cas, vous pouvez utiliser du code non sécurisé pour dire au compilateur “fais-moi confiance, je sais ce que je fait”. Le prix à payer pour cela est que vous l'utilisez à vos risques et périls : si vous écrivez du code non sécurisé de manière incorrecte, des problèmes liés à la sécurité de la mémoire peuvent se produire, tel qu'un déréférencement d'un pointeur vide.
Une autre raison pour laquelle Rust embarque son alter-ego non sécurisé est que le matériel des ordinateurs sur lequel il repose n'est pas sécurisé par essence. Si Rust ne vous laissait pas procéder à des opérations non sécurisées, vous ne pourriez pas faire certaines choses. Rust doit pouvoir vous permettre de développer du code bas-niveau, comme pouvoir interagir directement avec le système d'exploitation ou même écrire votre propre système d'exploitation. Pouvoir travailler avec des systèmes bas-niveau est un des objectifs du langage. Voyons ce que nous pouvons faire avec le Rust non sécurisé et comment le faire.
Les super-pouvoirs du code non sécurisé
Pour pouvoir utiliser le Rust non sécurisé, il faut utiliser le mot-clé unsafe
et ensuite créer un nouveau bloc qui contient le code non sécurisé. Vous pouvez
faire cinq actions en Rust non sécurisé, qui s'appellent les super-pouvoirs du
non sécurisé, actions que vous ne pourriez pas faire en Rust sécurisé. Ces super-pouvoirs
permettent de :
- Déréférencer un pointeur brut
- Faire appel à une fonction ou une méthode non sécurisée
- Lire ou modifier une variable statique mutable
- Implémenter un trait non sécurisé
- Accéder aux champs des
union
Il est important de comprendre que unsafe
ne désactive pas le vérificateur
d'emprunt et ne désactive pas les autres vérifications de sécurité de Rust : si
vous utilisez une référence dans du code non sécurisé, elle sera toujours
vérifiée. Le mot-clé unsafe
vous donne seulement accès à ces cinq
fonctionnalités qui ne sont alors pas vérifiées par le compilateur en vue de veiller
à la sécurité de la mémoire. Vous conservez donc un certain niveau de sécurité à
l'intérieur d'un bloc unsafe
.
De plus, unsafe
ne signifie pas que le code à l'intérieur du bloc est
obligatoirement dangereux ou qu'il va forcément présenter des problèmes de sécurité
mémoire : l'idée étant qu'en tant que développeur, vous vous assuriez que le code
à l'intérieur d'un bloc unsafe
va accéder correctement à la mémoire.
Personne n'est parfait, les erreurs arrivent, et en imposant que ces cinq
opérations non sécurisés se trouvent dans des blocs marqués d'un unsafe
, Rust
vous permet de savoir que ces éventuelles erreurs liées à la sécurité de la
mémoire se trouveront dans un bloc unsafe
. Vous devez donc essayer de
minimiser la taille des blocs unsafe
; vous ne le regretterez pas lorsque
vous rechercherez des bogues de mémoire.
Pour isoler autant que possible le code non sécurisé, il vaut mieux intégrer du
code non sécurisé dans une abstraction et fournir ainsi une API sécurisée,
comme nous le verrons plus tard dans ce chapitre lorsque nous examinerons les
fonctions et méthodes non sécurisées. Certaines parties de la bibliothèque
standard sont implémentées comme étant des abstractions sécurisées et basées
sur du code non sécurisé qui a été audité. Encapsuler du code non sécurisé dans
une abstraction sécurisée évite que l'utilisation de unsafe
ne se propage
dans des endroits où vous ou vos utilisateurs souhaiteraient éviter d'utiliser
les fonctionnalités du code unsafe
, car au final utiliser une abstraction
sécurisée doit rester sûr.
Analysons ces cinq super-pouvoirs à tour de rôle. Nous allons aussi découvrir quelques abstractions qui fournissent une interface sécurisée pour faire fonctionner du code non sécurisé.
Déréférencer un pointeur brut
Au chapitre 4, dans la section
“Les références pendouillantes”, nous
avions mentionné que le compilateur s'assure que les références sont toujours
valides. Le Rust non sécurisé offre deux nouveaux types qui s'appellent les
pointeurs brut et qui ressemblent aux références. Comme les références, les
pointeurs bruts peuvent être immuables ou mutables et s'écrivent respectivement
*const T
et *mut T
. L'astérisque n'est pas l'opérateur de déréférencement ;
il fait partie du nom du type. Dans un contexte de pointeur brut, immuable
signifie que le pointeur ne peut pas être affecté directement après avoir été
déréférencé.
Par rapport aux références et aux pointeurs intelligents, les pointeurs bruts peuvent :
- ignorer les règles d'emprunt en ayant plusieurs pointeurs tant immuables que mutables ou en ayant plusieurs pointeurs mutables qui pointent vers le même endroit.
- ne pas être obligés de pointer sur un emplacement mémoire valide
- être autorisés à avoir la valeur nulle
- ne pas implémenter de fonctionnalité de nettoyage automatique
En renonçant à ce que Rust fasse respecter ces garanties, vous pouvez sacrifier la sécurité garantie pour obtenir de meilleures performances ou avoir la possibilité de vous interfacer avec un autre langage ou matériel pour lesquels les garanties de Rust ne s'appliquent pas.
L'encart 19-1 montre comment créer un pointeur brut immuable et mutable à partir de références.
fn main() { let mut nombre = 5; let r1 = &nombre as *const i32; let r2 = &mut nombre as *mut i32; }
Remarquez que nous n'incorporons pas le mot-clé unsafe
dans ce code. Nous
pouvons créer des pointeurs bruts dans du code sécurisé ; nous ne pouvons
simplement pas déréférencer les pointeurs bruts à l'extérieur d'un bloc non
sécurisé, comme vous allez le constater d'ici peu.
Nous avons créé des pointeurs bruts en utilisant as
pour transformer les
références immuables et mutables en leur type de pointeur brut correspondant.
Comme nous les avons créés directement à partir de références qui sont garanties
d'être valides, nous savons que ces pointeurs bruts seront valides, mais nous ne
pouvons pas faire cette supposition sur tous les pointeurs bruts.
Ensuite, nous allons créer un pointeur brut dont la validité n'est pas certaine. L'encart 19-2 montre comment créer un pointeur brut vers un emplacement arbitraire de la mémoire. Essayer d'utiliser de la mémoire arbitraire va engendrer un comportement incertain : il peut y avoir des données à cette adresse comme il peut ne pas y en avoir, le compilateur pourrait optimiser le code de tel sorte qu'aucun accès mémoire n'aura lieu ou bien le programme pourrait déclencher une erreur de segmentation. Habituellement, il n'y a pas de bonne raison d'écrire du code comme celui-ci, mais c'est possible.
fn main() { let addresse = 0x012345usize; let r = addresse as *const i32; }
Souvenez-vous que nous pouvons créer des pointeurs bruts dans du code sécurisé,
mais que nous ne pouvons pas y déréférencer les pointeurs bruts et lire les
données sur lesquelles ils pointent. Dans l'encart 19-3, nous utilisons
l'opérateur de déréférencement *
sur un pointeur brut qui nécessite un bloc
unsafe
.
fn main() { let mut nombre = 5; let r1 = &nombre as *const i32; let r2 = &mut nombre as *mut i32; unsafe { println!("r1 vaut : {}", *r1); println!("r2 vaut : {}", *r2); } }
La création de pointeur ne pose pas de problèmes ; c'est seulement lorsque nous essayons d'accéder aux valeurs sur lesquelles ils pointent qu'on risque d'obtenir une valeur invalide.
Remarquez aussi que dans les encarts 19-1 et 19-3, nous avons créé les
pointeurs bruts *const i32
et *mut i32
qui pointent tous les deux au même
endroit de la mémoire, où nombre
est stocké. Si nous avions plutôt tenté de
créer une référence immuable et une mutable vers nombre
, le code n'aurait pas
compilé à cause des règles de possession de Rust qui ne permettent pas d'avoir
une référence mutable en même temps qu'une ou plusieurs références immuables.
Avec les pointeurs bruts, nous pouvons créer un pointeur mutable et un pointeur
immuable vers le même endroit et changer la donnée via le pointeur mutable, en
risquant un accès concurrent. Soyez vigilant !
Avec tous ces dangers, pourquoi vous risquer à utiliser les pointeurs bruts ? Une des utilisations principale consiste à s'interfacer avec du code C, comme vous allez le découvrir dans la section suivante. Une autre utilisation est de nous permettre de créer une abstraction sécurisée que le vérificateur d'emprunt ne comprend pas. Nous allons découvrir les fonctions non sécurisées puis voir un exemple d'une abstraction sécurisée qui utilise du code non sécurisé.
Faire appel à une fonction ou une méthode non sécurisée
Le deuxième type d'opération qui nécessite un bloc unsafe
est l'appel à des
fonctions non sécurisées. Les fonctions et méthodes non sécurisées ressemblent
exactement aux méthodes et fonctions habituelles, mais ont un unsafe
en plus
devant le reste de leur définition. Le mot-clé unsafe
dans ce cas signifie
que la fonction a des exigences que nous devons respecter pour pouvoir y faire
appel, car Rust ne pourra pas garantir de son côté que nous les ayons remplies.
En faisant appel à une fonction non sécurisée dans un bloc unsafe
, nous
reconnaissons que nous avons lu la documentation de cette fonction et pris la
responsabilité de respecter les conditions d'utilisation de la fonction.
Voici une fonction non sécurisée dangereux
, qui ne fait rien dans son corps :
fn main() { unsafe fn dangereux() {} unsafe { dangereux(); } }
Nous devons faire appel à la fonction dangereux
dans un bloc unsafe
séparé.
Si nous essayons d'appeler dangereux
sans le bloc unsafe
, nous obtenons une
erreur :
$ cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> src/main.rs:4:5
|
4 | dangereux();
| ^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
For more information about this error, try `rustc --explain E0133`.
error: could not compile `unsafe-example` due to previous error
En ajoutant le bloc unsafe
autour de notre appel à dangereux
, nous
déclarons à Rust que nous avons lu la documentation de la fonction, que nous
comprenons comment l'utiliser correctement et que nous avons vérifié que nous
répondons bien aux exigences de la fonction.
Les corps des fonctions non sécurisées sont bel et bien des blocs unsafe
,
donc pour pouvoir procéder à d'autres opérations non sécurisées dans une
fonction non sécurisée, nous n'avons pas besoin d'ajouter un autre bloc
unsafe
.
Créer une abstraction sécurisée sur du code non sécurisé
Ce n'est pas parce qu'une fonction contient du code non sécurisé que nous devons
forcément marquer l'intégralité de cette fonction comme non sécurisée. En fait,
envelopper du code non sécurisé dans une fonction sécurisée est une abstraction
courante. Par exemple, étudions une fonction de la bibliothèque standard,
split_at_mut
, qui nécessite du code non sécurisé, et étudions comment nous
devrions l'implémenter. Cette méthode sécurisée est définie sur des slices
mutables : elle prend une slice en paramètre et en créée deux autres en divisant
la slice à l'indice donné en argument. L'encart 19-4 montre comment utiliser
split_at_mut
.
fn main() { let mut v = vec![1, 2, 3, 4, 5, 6]; let r = &mut v[..]; let (a, b) = r.split_at_mut(3); assert_eq!(a, &mut [1, 2, 3]); assert_eq!(b, &mut [4, 5, 6]); }
Nous ne pouvons pas implémenter cette fonction en utilisant uniquement du Rust
sécurisé. Une tentative en ce sens ressemblerait à l'encart 19-5, qui ne se
compilera pas. Par simplicité, nous allons implémenter split_at_mut
comme une
fonction plutôt qu'une méthode et seulement pour des slices de valeurs i32
au
lieu d'un type générique T
.
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
assert!(mid <= len);
(&mut values[..mid], &mut values[mid..])
}
fn main() {
let mut vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
}
Cette fonction commence par obtenir la longueur totale de la slice. Elle vérifie ensuite que l'indice donné en paramètre est bien à l'intérieur de la slice en vérifiant s'il est inférieur ou égal à la longueur. La vérification implique que si nous envoyons un indice qui est plus grand que la longueur de la slice à découper, la fonction va paniquer avant d'essayer d'utiliser cet indice.
Ensuite, nous retournons deux slices mutables dans un tuple : une à partir du
début de la slice initiale jusqu'à l'indice mod
et une autre à partir de
l'indice jusqu'à la fin de la slice.
Lorsque nous essayons de compiler le code de l'encart 19-5, nous allons obtenir une erreur.
$ cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0499]: cannot borrow `*values` as mutable more than once at a time
--> src/main.rs:6:30
|
1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
| - let's call the lifetime of this reference `'1`
...
6 | (&mut values[..mid], &mut values[mid..])
| --------------------------^^^^^^--------
| | | |
| | | second mutable borrow occurs here
| | first mutable borrow occurs here
| returning this value requires that `*values` is borrowed for `'1`
For more information about this error, try `rustc --explain E0499`.
error: could not compile `unsafe-example` due to previous error
Le vérificateur d'emprunt de Rust ne comprend pas que nous empruntons différentes parties de la slice ; il comprend seulement que nous empruntons la même slice à deux reprises. L'emprunt de différentes parties d'une slice ne pose fondamentalement pas de problèmes car les deux slices ne se chevauchent pas, mais Rust n'est pas suffisamment intelligent pour comprendre ceci. Lorsque nous savons que ce code est correct, mais que Rust ne le sait pas, il est approprié d'utiliser du code non sécurisé.
L'encart 19-6 montre comment utiliser un bloc unsafe
, un pointeur brut, et
quelques appels à des fonctions non sécurisées pour construire une
implémentation de split_at_mut
qui fonctionne.
use std::slice; fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = slice.len(); let ptr = slice.as_mut_ptr(); assert!(mid <= len); unsafe { ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } } fn main() { let mut vector = vec![1, 2, 3, 4, 5, 6]; let (left, right) = split_at_mut(&mut vector, 3); }
Souvenez-vous de la section “Le type slice” du
chapitre 4 dans laquelle nous avions dit qu'une slice est définie par un pointeur
vers une donnée ainsi qu'une longueur de la slice. Nous avons utilisé la méthode
len
pour obtenir la longueur d'une slice ainsi que la méthode as_mut_ptr
pour accéder au pointeur brut d'une slice. Dans ce cas, comme nous avons une
slice mutable de valeurs i32
, as_mut_ptr
retourne un pointeur brut avec le
type *mut i32
que nous stockons dans la variable ptr
.
Nous avons conservé la vérification que l'indice mid
soit dans la slice.
Ensuite, nous utilisons le code non sécurisé : la fonction
slice::from_raw_parts_mut
prend en paramètre un pointeur brut et une longueur,
et elle créée une slice. Nous utilisons cette fonction pour créer une slice qui
débute à ptr
et qui est longue de mid
éléments. Ensuite nous faisons appel à
la méthode add
sur ptr
avec mid
en argument pour obtenir un pointeur
brut qui démarre à mid
, et nous créons une slice qui utilise ce pointeur et
le nombre restant d'éléments après mid
comme longueur.
La fonction slice::from_raw_parts_mut
est non sécurisée car elle prend en
argument un pointeur brut et doit avoir confiance en la validité de ce pointeur.
La méthode add
sur les pointeurs bruts est aussi non sécurisée, car elle
doit croire que l'emplacement décalé est aussi un pointeur valide. Voilà
pourquoi nous avons placé un bloc unsafe
autour de nos appels à
slice::from_raw_parts_mut
et add
afin que nous puissions les effectuer. En
analysant le code et en ayant ajouté la vérification que mid
doit être
inférieur ou égal à len
, nous pouvons affirmer que tous les pointeurs bruts
utilisés dans le bloc unsafe
sont des pointeurs valides vers les données de la
slice. C'est une utilisation acceptable et appropriée de unsafe
.
Remarquez que nous n'avons pas eu besoin de marquer la fonction résultante
split_at_mut
comme étant unsafe
, et que nous pouvons faire appel à cette
fonction dans du code Rust sécurisé. Nous avons créé une abstraction sécurisée
du code non sécurisé avec une implémentation de la fonction qui utilise de
manière sécurisée du code non sécurisé, car elle créée uniquement des pointeurs
valides à partir des données auxquelles cette fonction a accès.
En contre-partie, l'utilisation de slice::from_raw_parts_mut
dans l'encart
19-7 peut planter lorsque la slice sera utilisée. Ce code prend un emplacement
arbitraire dans la mémoire et crée un slice de 10 000 éléments.
fn main() { use std::slice; let addresse = 0x01234usize; let r = addresse as *mut i32; let valeurs: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) }; }
Nous ne possédons pas la mémoire à cet emplacement arbitraire, et il n'y a
aucune garantie que la slice créée par ce code contiennent des valeurs i32
valides. Toute tentative d'utilisation de valeurs
aura un comportement
imprévisible bien qu'il s'agisse d'une slice valide.
Utiliser des fonctions extern
pour faire appel à du code externe
Parfois, votre code Rust peut avoir besoin d'interagir avec du code écrit dans
d'autres langages. Dans ce cas, Rust propose un mot-clé, extern
, qui facilite
la création et l'utilisation du Foreign Function Interface (FFI). Le FFI est
un outil permettant à un langage de programmation de définir des fonctions auxquelles
d'autres langages de programmation pourront faire appel.
L'encart 19-8 montre comment configurer l'intégration de la fonction abs
de la
bibliothèque standard du C. Les fonctions déclarées dans des blocs extern
sont toujours non sécurisées lorsqu'on les utilise dans du code Rust. La raison
à cela est que les autres langages n'appliquent pas les règles et garanties de
Rust, Rust ne peut donc pas les vérifier, si bien que la responsabilité de s'assurer
de la sécurité revient au développeur.
Fichier : src/main.rs
extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("La valeur absolue de -3 selon le langage C : {}", abs(-3)); } }
Au sein du bloc extern "C"
, nous listons les noms et les signatures des
fonctions externes de l'autre langage que nous souhaitons solliciter. La partie
"C" définit quelle est l'application binary interface (ABI) que la fonction
doit utiliser : l'ABI définit comment faire appel à la fonction au niveau
assembleur. L'ABI "C"
est la plus courante et respecte l'ABI du langage de
programmation C.
Faire appel à des fonctions Rust dans d'autres langages
Nous pouvons aussi utiliser
extern
pour créer une interface qui permet à d'autres langages de faire appel à des fonctions Rust. Au lieu d'avoir un blocextern
, nous ajoutons le mot-cléextern
et nous renseignons l'ABI à utiliser juste avant le mot-cléfn
. Nous avons aussi besoin d'ajouter l'annotation#[no_mangle]
pour dire au compilateur Rust de ne pas déformer le nom de cette fonction. La déformation s'effectue lorsqu'un compilateur change le nom que nous avons donné à une fonction pour un nom qui contient plus d'informations pour d'autres étapes du processus de compilation, mais qui est moins lisible par l'humain. Tous les compilateurs de langages de programmation déforment les noms de façon légèrement différente, donc pour que le nom d'une fonction Rust soit utilisable par d'autres langages, nous devons désactiver la déformation du nom par le compilateur de Rust.
Lire ou modifier une variable statique mutable
Jusqu'à présent, nous n'avons pas parlé des variables globales, que Rust accepte mais qui peuvent poser des problèmes avec les règles de possession de Rust. Si deux tâches accèdent en même temps à la même variable globale, cela peut causer un accès concurrent.
En Rust, les variables globales s'appellent des variables statiques. L'encart 19-9 montre un exemple de déclaration et d'utilisation d'une variable statique avec une slice de chaîne de caractères comme valeur.
Fichier : src/main.rs
static HELLO_WORLD: &str = "Hello, world!"; fn main() { println!("Cela vaut : {}", HELLO_WORLD); }
Les variables statiques ressemblent aux constantes, que nous avons vues dans la
section
“Différences entre les variables et les constantes”
du chapitre 3. Les noms des variables statiques sont par convention en
SCREAMING_SNAKE_CASE
. Les variables statiques
peuvent uniquement stocker des références ayant la durée de vie 'static
, de façon
à ce que le compilateur Rust puisse la déterminer tout seul et que nous
n'ayons pas besoin de la renseigner explicitement. L'accès à une variable
statique immuable est sécurisé.
Les constantes et les variables statiques immuables se ressemblent, mais leur différence subtile est que les valeurs dans les variables statiques ont une adresse fixe en mémoire. L'utilisation de sa valeur va toujours accéder à la même donnée. Les constantes en revanche, peuvent reproduire leurs données à chaque fois qu'elles sont utilisées.
Une autre différence entre les constantes et les variables statiques est que
les variables statiques peuvent être mutables. Lire et modifier des variables
statiques mutables est non sécurisé. L'encart 19-10 montre comment déclarer,
lire et modifier la variable statique mutable COMPTEUR
.
Fichier : src/main.rs
static mut COMPTEUR: u32 = 0; fn ajouter_au_compteur(valeur: u32) { unsafe { COMPTEUR += valeur; } } fn main() { ajouter_au_compteur(3); unsafe { println!("COMPTEUR : {}", COMPTEUR); } }
Comme avec les variables classiques, nous renseignons la mutabilité en
utilisant le mot-clé mut
. Tout code qui lit ou modifie COMPTEUR
doit se
trouver dans un bloc unsafe
. Ce code se compile et affiche COMPTEUR : 3
comme nous l'espérions car nous n'avons qu'une seule tâche. Si nous avions
plusieurs tâches qui accèdent à COMPTEUR
, nous pourrions avoir un accès
concurrent.
Avec les données mutables qui sont accessibles globalement, il devient difficile de s'assurer qu'il n'y a pas d'accès concurrent, c'est pourquoi Rust considère les variables statiques mutables comme étant non sécurisées. Lorsque c'est possible, il vaut mieux utiliser les techniques de concurrence et les pointeurs intelligents adaptés au multitâche que nous avons vus au chapitre 16, afin que le compilateur puisse vérifier que les données qu'utilisent les différentes tâches sont sécurisées.
Implémenter un trait non sécurisé
Un autre cas d'usage de unsafe
est l'implémentation d'un trait non sécurisé.
Un trait n'est pas sécurisé lorsque au moins une de ses méthodes contient une
invariante que le compilateur ne peut pas vérifier. Nous pouvons déclarer un
trait qui n'est pas sécurisé en ajoutant le mot-clé unsafe
devant trait
et
en marquant aussi l'implémentation du trait comme unsafe
, comme dans
l'encart 19-11.
unsafe trait Foo { // les méthodes vont ici } unsafe impl Foo for i32 { // les implémentations des méthodes vont ici } fn main() {}
En utilisant unsafe impl
, nous promettons que nous veillons aux invariantes
que le compilateur ne peut pas vérifier.
Par exemple, souvenez-vous des traits Sync
et Send
que nous avions découverts
dans une section du
chapitre 16 : le compilateur implémente automatiquement ces traits si nos types
sont entièrement composés des types Send
et Sync
. Si nous implémentions un
type qui contenait un type qui n'était pas Send
ou Sync
, tel que les
pointeurs bruts, et nous souhaitions marquer ce type comme étant Send
ou
Sync
, nous aurions dû utiliser unsafe
. Rust ne peut pas vérifier que notre
type respecte les garanties pour que ce type puisse être envoyé en toute
sécurité entre des tâches ou qu'il puisse être utilisé par plusieurs tâches ;
en conséquence, nous avons besoin de faire ces vérifications manuellement et le
signaler avec unsafe
.
Utiliser des champs d'un Union
La dernière action qui fonctionne uniquement avec unsafe
est d'accéder aux
champs d'un union. Un union
ressemble à une struct
, mais un seul champ de
ceux déclarés est utilisé dans une instance précise au même moment. Les unions
sont principalement utilisés pour s'interfacer avec les unions du code C.
L'accès aux champs des unions n'est pas sécurisé car Rust ne peut pas garantir
le type de la donnée qui est actuellement stockée dans l'instance de l'union.
Vous pouvez en apprendre plus sur les unions dans
the Rust Reference.
Quand utiliser du code non sécurisé
L'utilisation de unsafe
pour mettre en oeuvre une des cinq actions (ou
super-pouvoirs) que nous venons d'aborder n'est pas une mauvaise chose et ne doit
pas être mal vu. Mais il est plus difficile de sécuriser du code unsafe
car le
compilateur ne peut pas aider à garantir la sécurité de la mémoire. Lorsque vous
avez une bonne raison d'utiliser du code non sécurisé, vous pouvez le faire, et
vous aurez l'annotation explicite unsafe
pour faciliter la recherche de la
source des problèmes lorsqu'ils surviennent.