Section courante

A propos

Section administrative du site

unsafe

Non sécuritaire
Rust

Syntaxe

unsafe fn action_dangereuse() {
   // code non sécurisé ici
}

Paramètres

Nom Description
action_dangereuse() Ce paramètre permet de déclarer une fonction contenant du code qui peut violer les garanties de sécurité de Rust et nécessite une manipulation explicite et prudente.
code non sécurisé ici Ce paramètre permet d'indiquer un bloc de code où des opérations non sécurisées (comme la manipulation de pointeurs bruts) sont autorisées.

Description

Ce mot réservé permet de dénoter du code, des fonctions, des caractéristiques ou des mises en oeuvre non sécurisés.

Remarques

Code ou interfaces dont la sécurité mémoire ne peut être vérifiée par le système de types.

Le mot clef unsafe a deux utilisations :

Elles ne s'excluent pas mutuellement, comme le montre l'exemple d'un bloc non sécurisé : le corps d'un bloc non sécurisé est, par défaut, traité comme un bloc non sécurisé. L'option de contrôle unsafe_op_in_unsafe_fn peut être activée pour modifier ce comportement.

Capacités non sécurisées

Quoi qu'il en soit, Rust sûr ne peut pas provoquer de comportement indéfini. C'est ce qu'on appelle la solidité : un programme bien typé possède effectivement les propriétés souhaitées. Le Nomicon propose une explication plus détaillée à ce sujet.

Pour garantir sa solidité, Rust sûr est suffisamment restreint pour être vérifié automatiquement. Cependant, il est parfois nécessaire d'écrire du code correct pour des raisons trop complexes pour être comprises par le compilateur. Dans ce cas, vous devez utiliser Rust non sûr.

Voici les capacités de Rust non sûr, en plus de Rust sûr :

Cependant, ce pouvoir supplémentaire s'accompagne de responsabilités supplémentaires : il vous appartient désormais de garantir la fiabilité. Le mot clef unsafe permet d'identifier clairement les parties de code concernées.

Les différentes significations de unsafe

Toutes les utilisations de unsafe ne sont pas équivalentes : certaines servent à signaler l'existence d'un contrat que le programmeur doit vérifier, d'autres à indiquer : « J'ai vérifié le contrat, allez-y ». La discussion suivante sur les internes de Rust fournit des explications plus détaillées à ce sujet, mais voici un résumé des points principaux :

Par défaut, unsafe fn agit également comme un bloc unsafe {} autour du code de la fonction. Cela signifie qu'il ne s'agit pas seulement d'un signal envoyé à l'appelant, mais aussi d'une garantie que les conditions préalables aux opérations de la fonction sont respectées. Mélanger ces deux significations peut prêter à confusion ; c'est pourquoi le lint unsafe_op_in_unsafe_fn peut être activé pour avertir contre ce problème et exiger des blocs unsafe explicites, même à l'intérieur d'unsafe fn.

Exemples

Marquer des éléments comme non sécurisés.

Unsafe peut être utilisé pour les fonctions. Notez que les fonctions et les statics déclarées dans des blocs externes sont implicitement marquées comme non sécurisées (mais pas les fonctions déclarées comme extern "quelque chose" fn ...). Les statics mutables sont toujours non sécurisées, où qu'elles soient déclarées. Les méthodes peuvent également être déclarées comme non sécurisées :

  1. static mut FOO: &str = "bonjour";
  2.  
  3. unsafe fn unsafe_fn() {}
  4.  
  5. unsafe extern "C" {
  6.     fn unsafe_extern_fn();
  7.     static BAR: *mut u32;
  8. }
  9.  
  10. trait SafeTraitWithUnsafeMethod {
  11.     unsafe fn unsafe_method(&self);
  12. }
  13.  
  14. struct S;
  15.  
  16. impl S {
  17.     unsafe fn unsafe_method_on_struct() {}
  18. }

Les traits peuvent également être déclarés comme dangereux :

  1. unsafe trait UnsafeTrait {}

Étant donné que les fonctions fn et trait unsafe indiquent l'existence d'un contrat de sécurité que le compilateur ne peut pas appliquer, il est important de le documenter. La bibliothèque standard en propose de nombreux exemples, comme celui-ci, extrait de Vec::set_len. La section # Safety explique le contrat à respecter pour appeler la fonction en toute sécurité.

  1. /// Force la longueur du vecteur à `new_len`.
  2. ///
  3. /// Il s'agit d'une opération de bas niveau ne conservant aucun des invariants normaux du type. La modification de la longueur 
  4. /// d'un vecteur s'effectue généralement à l'aide d'une opération sûre, telle que «truncate», «resize», «extend» ou «clear».
  5. ///
  6. /// # Sécuritaire
  7. ///
  8. /// - `new_len` doit être inférieur ou égal à `capacity()`.
  9. /// - Les éléments de `old_len..new_len` doivent être initialisés
  10.  
  11. pub unsafe fn set_len(&mut self, new_len: usize)

Utilisation de blocs et d'implémentations non sécurisés

L'exécution d'opérations non sécurisées nécessite un bloc non sécurisé :

  1. #![deny(unsafe_op_in_unsafe_fn)]
  2.  
  3. /// Déréférencer le pointeur donné.
  4. ///
  5. /// # Sécurité
  6. ///
  7. /// `ptr` doit être aligné et ne doit pas être suspendu.
  8.  
  9. unsafe fn deref_unchecked(ptr: *const i32) -> i32 {
  10.     // SÉCURITAIRE : l'appelant doit s'assurer que «ptr» est aligné et déréférençable.
  11.     unsafe { *ptr }
  12. }
  13.  
  14. let a = 3;
  15. let b = &a as *const _;
  16. // SÉCURITAIRE : « a » n'a pas été supprimé et les références sont toujours alignées, donc « b » est une adresse valide.
  17. unsafe { assert_eq!(*b, deref_unchecked(b)); };

unsafe et trait

Les interactions entre unsafe et trait peuvent être surprenantes. Comparons donc les deux combinaisons de fonctions safe dans un trait unsafe et unsafe dans un trait safe à l'aide de deux exemples :

  1. /// # Sécuritaire
  2. ///
  3. /// `make_even` doit renvoyer un nombre pair.
  4. unsafe trait MakeEven {
  5.     fn make_even(&self) -> i32;
  6. }
  7.  
  8. // SÉCURITAIRE : Notre `make_even` renvoie toujours quelque chose de pair.
  9. unsafe impl MakeEven for i32 {
  10.     fn make_even(&self) -> i32 {
  11.         self << 1
  12.     }
  13. }
  14.  
  15. fn use_make_even(x: impl MakeEven) {
  16.     if x.make_even() % 2 == 1 {
  17.         // SÉCURITAIRE : cela ne peut jamais arriver, car toutes les implémentations de « MakeEven » garantissent que « make_even » renvoie quelque chose d'égal.
  18.         unsafe { std::hint::unreachable_unchecked() };
  19.     }
  20. }

Notez que le contrat de sécurité du trait est respecté par l'implémentation et est lui-même utilisé pour respecter le contrat de sécurité de la fonction non sécurisée unreachable_unchecked appelée par use_make_even. make_even est une fonction sûre, car ses appelants n'ont pas à se soucier d'un contrat ; seule l'implémentation de MakeEven est requise pour respecter un contrat spécifique. use_make_even est sûre, car elle peut utiliser la promesse des implémentations de MakeEven pour respecter le contrat de sécurité de la fonction non sécurisée unreachable_unchecked qu'elle appelle.

Il est également possible d'avoir une fonction non sécurisée dans un trait sûr standard :

  1. #![deny(unsafe_op_in_unsafe_fn)]
  2.  
  3. trait Indexable {
  4.     const LEN: usize;
  5.  
  6.     /// # Safety
  7.     ///
  8.     /// L'appelant doit s'assurer que « idx < LEN ».
  9.     unsafe fn idx_unchecked(&self, idx: usize) -> i32;
  10. }
  11.  
  12. // L'implémentation de « i32 » n'a pas besoin d'effectuer de raisonnement contractuel.
  13. impl Indexable for i32 {
  14.     const LEN: usize = 1;
  15.  
  16.     unsafe fn idx_unchecked(&self, idx: usize) -> i32 {
  17.         debug_assert_eq!(idx, 0);
  18.         *self
  19.     }
  20. }
  21.  
  22. // L'implémentation pour les tableaux exploite le contrat de fonction pour utiliser « get_unchecked » sur les tranches et éviter une vérification à l'exécution.
  23. impl Indexable for [i32; 42] {
  24.     const LEN: usize = 42;
  25.  
  26.     unsafe fn idx_unchecked(&self, idx: usize) -> i32 {
  27.         // SAFETY: Conformément à la documentation de ce trait, l'appelant s'assure
  28.         // that `idx < 42`.
  29.         unsafe { *self.get_unchecked(idx) }
  30.     }
  31. }
  32.  
  33. // L'implémentation du type never déclare une longueur de 0, ce qui signifie que `idx_unchecked` ne peut jamais être appelé.
  34. impl Indexable for ! {
  35.     const LEN: usize = 0;
  36.  
  37.     unsafe fn idx_unchecked(&self, idx: usize) -> i32 {
  38.         // SAFETY: Selon la documentation de ce trait, l'appelant garantit que «idx < 0», ce qui est impossible, il s'agit donc d'un code mort.
  39.         unsafe { std::hint::unreachable_unchecked() }
  40.     }
  41. }
  42.  
  43. fn use_indexable<I: Indexable>(x: I, idx: usize) -> i32 {
  44.     if idx < I::LEN {
  45.         // SAFETY: We have checked that `idx < I::LEN`.
  46.         unsafe { x.idx_unchecked(idx) }
  47.     } else {
  48.         panic!("index out-of-bounds")
  49.     }
  50. }

Cette fois, use_indexable est sûr, car il utilise une vérification à l'exécution pour exécuter le contrat de sécurité d'idx_unchecked. L'implémentation d'Indexable est sûre, car lors de l'écriture d'idx_unchecked, nous n'avons pas à nous soucier de cela : nos appelants doivent exécuter une obligation de preuve (comme use_indexable), mais l'implémentation de get_unchecked n'a aucune obligation de preuve à respecter. Bien sûr, l'implémentation d'Indexable peut choisir d'appeler d'autres opérations non sécurisées, et elle a alors besoin d'un bloc non sécurisé pour indiquer qu'elle a exécuté les obligations de preuve de ses appelants. (Nous avons activé unsafe_op_in_unsafe_fn, de sorte que le corps d'idx_unchecked n'est pas implicitement un bloc non sécurisé.) Pour cela, elle peut utiliser le contrat que tous ses appelants doivent respecter : idx < LEN.

Formellement, une fonction non sécurisée dans un trait est une fonction dont les préconditions vont au-delà de celles encodées par les types de paramètres (comme idx < LEN), tandis qu'un trait non sécurisé peut déclarer que certaines de ses fonctions ont des postconditions allant au-delà de celles encodées par le type de retour (comme renvoyer un entier pair). Si un trait nécessite une fonction avec à la fois une précondition et une postcondition supplémentaires, il doit alors inclure une fonction non sécurisée dans un trait non sécurisé.



Dernière mise à jour : Vendredi, le 1er Août 2025