Explorer les pouvoirs surnaturels du Rust non sécurisé

Beginner

This tutorial is from open-source community. Access the source code

Introduction

Bienvenue dans Unsafe Rust. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, nous allons explorer le Rust non sécurisé, une fonctionnalité qui nous permet de contourner les garanties de sécurité mémoire imposées à la compilation et de nous donner des super-pouvoirs supplémentaires, tout en comprenant les risques et les responsabilités associés à son utilisation.

Unsafe Rust

Tout le code que nous avons discuté jusqu'à présent a bénéficié des garanties de sécurité mémoire de Rust imposées à la compilation. Cependant, Rust a un second langage caché en son sein qui ne impose pas ces garanties de sécurité mémoire : il s'agit du Rust non sécurisé et fonctionne comme le Rust classique, mais nous donne des super-pouvoirs supplémentaires.

Le Rust non sécurisé existe parce que, par nature, l'analyse statique est conservatrice. Lorsque le compilateur tente de déterminer si le code respecte les garanties, il est préférable qu'il rejette certains programmes valides plutôt qu'il n'accepte certains programmes invalides. Bien que le code puisse être correct, si le compilateur Rust n'a pas suffisamment d'informations pour être sûr, il rejettera le code. Dans ces cas, vous pouvez utiliser du code non sécurisé pour dire au compilateur : "Fiez-vous à moi, je sais ce que je fais." Soyez toutefois prévenu que vous utilisez le Rust non sécurisé à vos propres risques : si vous utilisez le code non sécurisé incorrectement, des problèmes peuvent survenir en raison de l'insécurité mémoire, comme la déréférencement d'un pointeur nul.

Une autre raison pour laquelle Rust a une alter ego non sécurisée est que le matériel informatique sous-jacent est intrinsèquement non sécurisé. Si Rust ne vous permettait pas d'effectuer des opérations non sécurisées, vous ne pourriez pas effectuer certaines tâches. Rust doit vous permettre de faire de la programmation système de bas niveau, comme interagir directement avec le système d'exploitation ou même écrire votre propre système d'exploitation. Travailler avec la programmation système de bas niveau est l'un des objectifs du langage. Explorons ce que nous pouvons faire avec le Rust non sécurisé et comment le faire.

Unsafe Superpowers

Pour passer au Rust non sécurisé, utilisez le mot clé unsafe puis commencez un nouveau bloc qui contient le code non sécurisé. Vous pouvez effectuer cinq actions en Rust non sécurisé que vous ne pouvez pas en Rust sécurisé, que nous appelons super-pouvoirs non sécurisés. Ces super-pouvoirs incluent la capacité de :

  1. Déréférencer un pointeur brut
  2. Appeler une fonction ou une méthode non sécurisée
  3. Accéder ou modifier une variable statique mutable
  4. Implémenter un trait non sécurisé
  5. Accéder aux champs des union

Il est important de comprendre que unsafe ne désactive pas le vérificateur d'emprunt ni aucune des 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 ne vous donne accès que à ces cinq fonctionnalités qui ne sont ensuite pas vérifiées par le compilateur pour la sécurité mémoire. Vous obtiendrez toujours un certain degré 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 nécessairement dangereux ou qu'il présentera certainement des problèmes de sécurité mémoire : l'idée est que, en tant que programmeur, vous vous assurerez que le code à l'intérieur d'un bloc unsafe accédera à la mémoire de manière valide.

Les gens sont fallibles et des erreurs se produiront, mais en exigeant que ces cinq opérations non sécurisées soient à l'intérieur de blocs annotés avec unsafe, vous saurez que tout problème lié à la sécurité mémoire doit être dans un bloc unsafe. Gardez les blocs unsafe petits ; vous serez reconnaissant plus tard lorsque vous investiguerez les bogues de mémoire.

Pour isoler le code non sécurisé le plus possible, il est préférable d'enfermer ce code dans une abstraction sécurisée et de fournir une API sécurisée, que nous aborderons plus tard dans le chapitre lorsque nous examinerons les fonctions et les méthodes non sécurisées. Des parties de la bibliothèque standard sont implémentées comme des abstractions sécurisées sur du code non sécurisé qui a été audité. Enveloppant le code non sécurisé dans une abstraction sécurisée empêche les utilisations de unsafe de se propager dans tous les endroits où vous ou vos utilisateurs voudriez utiliser la fonctionnalité implémentée avec du code non sécurisé, car utiliser une abstraction sécurisée est sécurisé.

Examillons chacun des cinq super-pouvoirs non sécurisés tour à tour. Nous examinerons également certaines abstractions qui offrent une interface sécurisée au code non sécurisé.

Dereferencing a Raw Pointer

Dans "Dangling References", nous avons mentionné que le compilateur assure que les références sont toujours valides. Le Rust non sécurisé a deux nouveaux types appelés pointeurs bruts qui sont similaires aux références. Comme pour les références, les pointeurs bruts peuvent être immuables ou mutables et sont écrits 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 le contexte des pointeurs bruts, immuable signifie que le pointeur ne peut pas être directement assigné après avoir été déréférencé.

Différents des références et des pointeurs intelligents, les pointeurs bruts :

  • Sont autorisés à ignorer les règles d'emprunt en ayant à la fois des pointeurs immuables et mutables ou plusieurs pointeurs mutables vers la même emplacement
  • Ne sont pas garantis pour pointer vers une mémoire valide
  • Sont autorisés à être nuls
  • N'implémentent pas de nettoyage automatique

En optant pour que Rust n'applique pas ces garanties, vous pouvez renoncer à la sécurité garantie en échange de meilleures performances ou de la capacité d'interagir avec un autre langage ou un matériel où les garanties de Rust ne s'appliquent pas.

Le Listing 19-1 montre comment créer un pointeur brut immuable et un pointeur brut mutable à partir de références.

let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

Listing 19-1 : Création de pointeurs bruts à partir de références

Remarquez que nous n'incluons 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 des pointeurs bruts en dehors d'un bloc unsafe, comme vous allez voir un peu plus loin.

Nous avons créé des pointeurs bruts en utilisant as pour convertir une référence immuable et une référence mutable dans leurs types de pointeurs bruts correspondants. Étant donné que nous les avons créés directement à partir de références garanties comme valides, nous savons que ces pointeurs bruts particuliers sont valides, mais nous ne pouvons pas faire cette hypothèse pour n'importe quel pointeur brut.

Pour le démontrer, ensuite, nous allons créer un pointeur brut dont la validité n'est pas aussi certaine. Le Listing 19-2 montre comment créer un pointeur brut vers un emplacement arbitraire en mémoire. Tenter d'utiliser une mémoire arbitraire est indéfini : il peut y avoir des données à cette adresse ou pas, le compilateur peut optimiser le code de sorte qu'il n'y ait pas d'accès mémoire, ou le programme peut se terminer avec une erreur de segmentation. En général, il n'y a pas de bonne raison d'écrire du code comme ça, mais il est possible.

let address = 0x012345usize;
let r = address as *const i32;

Listing 19-2 : Création d'un pointeur brut vers une adresse mémoire arbitraire

Rappelez-vous que nous pouvons créer des pointeurs bruts dans du code sécurisé, mais nous ne pouvons pas déréférencer des pointeurs bruts et lire les données auxquelles ils pointent. Dans le Listing 19-3, nous utilisons l'opérateur de déréférencement * sur un pointeur brut qui nécessite un bloc unsafe.

let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
    println!("r1 is: {}", *r1);
    println!("r2 is: {}", *r2);
}

Listing 19-3 : Déréférencement de pointeurs bruts à l'intérieur d'un bloc unsafe

Créer un pointeur ne fait aucun tort ; c'est seulement lorsque nous essayons d'accéder à la valeur à laquelle il pointe que nous risquons de finir par traiter une valeur invalide.

Notez également que dans les Listings 19-1 et 19-3, nous avons créé des pointeurs bruts *const i32 et *mut i32 qui ont tous deux pointé vers le même emplacement mémoire, où num est stocké. Si au lieu de cela, nous avions essayé de créer une référence immuable et une référence mutable à num, le code n'aurait pas compilé car les règles de propriété de Rust n'autorisent pas une référence mutable en même temps qu'aucune référence immuable. Avec des pointeurs bruts, nous pouvons créer un pointeur mutable et un pointeur immuable vers le même emplacement et modifier les données à travers le pointeur mutable, créant potentiellement une course de données. Faites attention!

Avec tous ces dangers, pourquoi utiliseriez-vous jamais des pointeurs bruts? Un cas d'utilisation majeur est lorsqu'on interface avec du code C, comme vous le verrez dans "Appeler une fonction ou une méthode non sécurisée". Un autre cas est lorsqu'on construit des abstractions sécurisées que le vérificateur d'emprunt ne comprend pas. Nous allons présenter les fonctions non sécurisées puis examiner un exemple d'abstraction sécurisée qui utilise du code non sécurisé.

Appeler une fonction ou une méthode non sécurisée

Le second type d'opération que vous pouvez effectuer dans un bloc unsafe est d'appeler des fonctions non sécurisées. Les fonctions et les méthodes non sécurisées ressemblent exactement à des fonctions et des méthodes normales, mais elles ont un unsafe supplémentaire avant le reste de la définition. Le mot clé unsafe dans ce contexte indique que la fonction a des exigences que nous devons respecter lorsque nous appelons cette fonction, car Rust ne peut pas garantir que nous avons rencontré ces exigences. En appelant une fonction non sécurisée à l'intérieur d'un bloc unsafe, nous disons que nous avons lu la documentation de cette fonction et que nous prenons la responsabilité de respecter les contrats de la fonction.

Voici une fonction non sécurisée nommée dangerous qui ne fait rien dans son corps :

unsafe fn dangerous() {}

unsafe {
    dangerous();
}

Nous devons appeler la fonction dangerous à l'intérieur d'un bloc unsafe séparé. Si nous essayons d'appeler dangerous sans le bloc unsafe, nous obtiendrons une erreur :

error[E0133]: call to unsafe function is unsafe and requires
unsafe function or block
 --> src/main.rs:4:5
  |
4 |     dangerous();
  |     ^^^^^^^^^^^ call to unsafe function
  |
  = note: consult the function's documentation for information on
how to avoid undefined behavior

Avec le bloc unsafe, nous affirmons à 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 respectons le contrat de la fonction.

Les corps de fonctions non sécurisées sont en fait des blocs unsafe, donc pour effectuer d'autres opérations non sécurisées à l'intérieur d'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é

Le simple fait qu'une fonction contienne du code non sécurisé ne signifie pas que nous devons marquer toute la 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 la fonction split_at_mut de la bibliothèque standard, qui nécessite du code non sécurisé. Nous explorerons comment nous pourrions l'implémenter. Cette méthode sécurisée est définie sur des slices mutables : elle prend une slice et la divise en deux en la coupant à l'index donné en argument. Le Listing 19-4 montre comment utiliser split_at_mut.

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]);

Listing 19-4 : Utilisation de la fonction sécurisée split_at_mut

Nous ne pouvons pas implémenter cette fonction en utilisant seulement le Rust sécurisé. Une tentative pourrait ressembler au Listing 19-5, qui ne compilera pas. Pour simplifier, nous implémenterons split_at_mut comme une fonction plutôt qu'une méthode et seulement pour des slices de valeurs i32 plutôt que pour 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..])
}

Listing 19-5 : Tentative d'implémentation de split_at_mut en utilisant seulement le Rust sécurisé

Cette fonction obtient d'abord la longueur totale de la slice. Ensuite, elle vérifie que l'index donné en paramètre est dans la slice en vérifiant s'il est inférieur ou égal à la longueur. L'assertion signifie que si nous passons un index supérieur à la longueur pour diviser la slice, la fonction va planter avant d'essayer d'utiliser cet index.

Ensuite, nous retournons deux slices mutables dans un tuple : l'une du début de la slice d'origine jusqu'à l'index mid et l'autre de mid jusqu'à la fin de la slice.

Lorsque nous essayons de compiler le code du Listing 19-5, nous obtiendrons une erreur :

error[E0499]: cannot borrow `*values` as mutable more than once at a time
 --> src/main.rs:9:31
  |
2 |     values: &mut [i32],
  |             - let's call the lifetime of this reference `'1`
...
9 |     (&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`

Le vérificateur d'emprunt de Rust ne peut pas comprendre que nous empruntons des parties différentes de la slice ; il ne sait que nous empruntons deux fois à la même slice. Emprunter des parties différentes d'une slice est fondamentalement correct car les deux slices ne se chevauchent pas, mais Rust n'est pas assez intelligent pour le savoir. Lorsque nous savons que le code est correct, mais que Rust ne le sait pas, il est temps d'utiliser du code non sécurisé.

Le Listing 19-6 montre comment utiliser un bloc unsafe, un pointeur brut et quelques appels à des fonctions non sécurisées pour que l'implémentation de split_at_mut fonctionne.

use std::slice;

fn split_at_mut(
    values: &mut [i32],
    mid: usize,
) -> (&mut [i32], &mut [i32]) {
  1 let len = values.len();
  2 let ptr = values.as_mut_ptr();

  3 assert!(mid <= len);

  4 unsafe {
        (
          5 slice::from_raw_parts_mut(ptr, mid),
          6 slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

Listing 19-6 : Utilisation de code non sécurisé dans l'implémentation de la fonction split_at_mut

Rappelez-vous de "Le type slice" qu'un slice est un pointeur vers des données et la longueur de la slice. Nous utilisons la méthode len pour obtenir la longueur d'une slice [1] et la méthode as_mut_ptr pour accéder au pointeur brut d'une slice [2]. Dans ce cas, puisque nous avons une slice mutable de valeurs i32, as_mut_ptr renvoie un pointeur brut avec le type *mut i32, que nous avons stocké dans la variable ptr.

Nous conservons l'assertion que l'index mid est dans la slice [3]. Ensuite, nous arrivons au code non sécurisé [4] : la fonction slice::from_raw_parts_mut prend un pointeur brut et une longueur, et elle crée une slice. Nous l'utilisons pour créer une slice qui commence à ptr et qui est longue de mid éléments [5]. Ensuite, nous appelons la méthode add sur ptr avec mid comme argument pour obtenir un pointeur brut qui commence à mid, et nous créons une slice en utilisant ce pointeur et le nombre restant d'éléments après mid comme longueur [6].

La fonction slice::from_raw_parts_mut est non sécurisée car elle prend un pointeur brut et doit faire confiance que ce pointeur est valide. La méthode add sur les pointeurs bruts est également non sécurisée car elle doit faire confiance que l'emplacement d'offset est également un pointeur valide. Par conséquent, nous avons dû mettre un bloc unsafe autour de nos appels à slice::from_raw_parts_mut et add pour pouvoir les appeler. En examinant le code et en ajoutant l'assertion que mid doit être inférieur ou égal à len, nous pouvons dire que tous les pointeurs bruts utilisés à l'intérieur du bloc unsafe seront des pointeurs valides vers des données à l'intérieur de la slice. Ceci est une utilisation acceptable et appropriée de unsafe.

Notez que nous n'avons pas besoin de marquer la fonction résultante split_at_mut comme unsafe, et nous pouvons appeler cette fonction à partir du Rust sécurisé. Nous avons créé une abstraction sécurisée pour le code non sécurisé avec une implémentation de la fonction qui utilise du code non sécurisé de manière sécurisée, car elle ne crée que des pointeurs valides à partir des données dont cette fonction a accès.

En revanche, l'utilisation de slice::from_raw_parts_mut dans le Listing 19-7 risque probablement de planter lorsque la slice est utilisée. Ce code prend un emplacement mémoire arbitraire et crée une slice de 10 000 éléments de long.

use std::slice;

let address = 0x01234usize;
let r = address as *mut i32;

let values: &[i32] = unsafe {
    slice::from_raw_parts_mut(r, 10000)
};

Listing 19-7 : Création d'une slice à partir d'un emplacement mémoire arbitraire

Nous ne possédons pas la mémoire à cet emplacement arbitraire, et il n'est pas garanti que la slice créée par ce code contienne des valeurs i32 valides. Tenter d'utiliser values comme si c'était une slice valide entraîne un comportement indéfini.

Utiliser des fonctions extern pour appeler du code externe

Parfois, votre code Rust peut avoir besoin d'interagir avec du code écrit dans un autre langage. Pour cela, Rust dispose du mot clé extern qui facilite la création et l'utilisation d'une Foreign Function Interface (FFI), qui est un moyen pour un langage de programmation de définir des fonctions et permettre à un autre (étranger) langage de programmation d'appeler ces fonctions.

Le Listing 19-8 montre comment configurer une intégration avec la fonction abs de la bibliothèque standard C. Les fonctions déclarées à l'intérieur de blocs extern sont toujours non sécurisées à appeler à partir du code Rust. La raison en est que les autres langages ne respectent pas les règles et les garanties de Rust, et Rust ne peut pas les vérifier, donc la responsabilité incombe au programmeur pour assurer la sécurité.

Nom du fichier : src/main.rs

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!(
            "Absolute value of -3 according to C: {}",
            abs(-3)
        );
    }
}

Listing 19-8 : Déclaration et appel d'une fonction extern définie dans un autre langage

Dans le bloc extern "C", nous listons les noms et les signatures des fonctions externes d'un autre langage que nous souhaitons appeler. La partie "C" définit quelle application binary interface (ABI) utilise la fonction externe : l'ABI définit comment appeler la fonction au niveau de l'assemblage. L'ABI "C" est la plus commune et suit l'ABI du langage de programmation C.

Appeler des fonctions Rust à partir d'autres langages

Nous pouvons également utiliser extern pour créer une interface qui permet à d'autres langages d'appeler des fonctions Rust. Au lieu de créer un bloc extern complet, nous ajoutons le mot clé extern et spécifions l'ABI à utiliser juste avant le mot clé fn pour la fonction concernée. Nous devons également ajouter une annotation #[no_mangle] pour dire au compilateur Rust de ne pas modifier le nom de cette fonction. Mangling est le fait qu'un compilateur change le nom que nous avons donné à une fonction en un nom différent qui contient plus d'informations pour d'autres parties du processus de compilation à consommer, mais est moins lisible pour l'homme. Chaque compilateur de langage de programmation modifie les noms légèrement différemment, donc pour qu'une fonction Rust soit nommable par d'autres langages, nous devons désactiver le mangling de noms du compilateur Rust.

Dans l'exemple suivant, nous rendons la fonction call_from_c accessible à partir du code C, après qu'elle ait été compilée en une bibliothèque partagée et liée à partir de C :

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

Cette utilisation de extern ne nécessite pas unsafe.

Accéder à ou modifier une variable statique mutable

Dans ce livre, nous n'avons pas encore parlé des variables globales, que Rust prend en charge mais qui peuvent poser des problèmes avec les règles de propriété de Rust. Si deux threads accèdent à la même variable globale mutable, cela peut entraîner une course de données.

En Rust, les variables globales sont appelées variables statiques. Le Listing 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.

Nom du fichier : src/main.rs

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("value is: {HELLO_WORLD}");
}

Listing 19-9 : Définition et utilisation d'une variable statique immuable

Les variables statiques sont similaires aux constantes, que nous avons discutées dans "Constantes". Les noms des variables statiques sont en SCREAMING_SNAKE_CASE par convention. Les variables statiques ne peuvent stocker que des références avec la durée de vie 'static, ce qui signifie que le compilateur Rust peut déterminer la durée de vie et que nous n'avons pas besoin d'y annoter explicitement. Accéder à une variable statique immuable est sécurisé.

Une différence subtile entre les constantes et les variables statiques immuables est que les valeurs dans une variable statique ont une adresse fixe en mémoire. Utiliser la valeur accédera toujours aux mêmes données. Les constantes, en revanche, sont autorisées à dupliquer leurs données chaque fois qu'elles sont utilisées. Une autre différence est que les variables statiques peuvent être mutables. Accéder et modifier les variables statiques mutables est non sécurisé. Le Listing 19-10 montre comment déclarer, accéder et modifier une variable statique mutable nommée COUNTER.

Nom du fichier : src/main.rs

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {COUNTER}");
    }
}

Listing 19-10 : Lire ou écrire dans une variable statique mutable est non sécurisé.

Comme pour les variables régulières, nous spécifions la mutabilité en utilisant le mot clé mut. Tout code qui lit ou écrit dans COUNTER doit être à l'intérieur d'un bloc unsafe. Ce code compile et affiche COUNTER: 3 comme prévu car c'est un code mono-threadé. Avoir plusieurs threads accéder à COUNTER entraînerait probablement des courses de données.

Avec des données mutables accessibles globalement, il est difficile de s'assurer qu'il n'y a pas de courses de données, c'est pourquoi Rust considère les variables statiques mutables comme non sécurisées. Lorsque cela est possible, il est préférable d'utiliser les techniques de concurrence et les pointeurs intelligents sécurisés par threads que nous avons discutés au Chapitre 16 afin que le compilateur vérifie que l'accès aux données à partir de différents threads est effectué de manière sécurisée.

Implémenter un trait non sécurisé

Nous pouvons utiliser unsafe pour implémenter un trait non sécurisé. Un trait est non sécurisé lorsqu'au moins une de ses méthodes a une certaine propriété que le compilateur ne peut pas vérifier. Nous déclarons qu'un trait est non sécurisé en ajoutant le mot clé unsafe avant trait et en marquant l'implémentation du trait comme unsafe également, comme montré dans le Listing 19-11.

unsafe trait Foo {
    // méthodes ici
}

unsafe impl Foo for i32 {
    // implémentations de méthodes ici
}

Listing 19-11 : Définition et implémentation d'un trait non sécurisé

En utilisant unsafe impl, nous promettons que nous respecterons les propriétés que le compilateur ne peut pas vérifier.

Par exemple, rappelez-vous les traits marqueurs Send et Sync que nous avons discutés dans "Concurrency extensible avec les traits Send et Sync" : le compilateur implémente automatiquement ces traits si nos types sont composés entièrement de types Send et Sync. Si nous implémentons un type qui contient un type qui n'est pas Send ou Sync, tel que des pointeurs bruts, et que nous voulons marquer ce type comme Send ou Sync, nous devons utiliser unsafe. Rust ne peut pas vérifier que notre type respecte les garanties qu'il peut être envoyé en toute sécurité entre les threads ou accès à partir de plusieurs threads ; par conséquent, nous devons effectuer ces vérifications manuellement et indiquer cela avec unsafe.

Accéder aux champs d'une union

La dernière opération qui ne fonctionne qu'avec unsafe est l'accès aux champs d'une union. Une union est similaire à un struct, mais seulement un champ déclaré est utilisé dans une instance particulière à un moment donné. Les unions sont principalement utilisées pour interfacer avec les unions dans le code C. Accéder aux champs d'une union est non sécurisé car Rust ne peut pas garantir le type des données actuellement stockées dans l'instance d'union. Vous pouvez en savoir plus sur les unions dans la Rust Reference à *https://doc.rust-lang.org/reference/items/unions.html\*\*.\*

Quand utiliser du code non sécurisé

Utiliser unsafe pour utiliser l'un des cinq pouvoirs surnaturels que nous venons de discuter n'est pas une erreur et n'est même pas déconseillé, mais il est plus difficile de corriger le code unsafe car le compilateur ne peut pas aider à maintenir la sécurité mémoire. Lorsque vous avez une raison d'utiliser du code unsafe, vous pouvez le faire, et avoir l'annotation unsafe explicite facilite la localisation de la source des problèmes lorsqu'ils se produisent.

Sommaire

Félicitations ! Vous avez terminé le laboratoire sur le Rust non sécurisé. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.