diff --git a/FRENCH/src/04_pinning/01_chapter.md b/FRENCH/src/04_pinning/01_chapter.md new file mode 100644 index 00000000..5aebfda4 --- /dev/null +++ b/FRENCH/src/04_pinning/01_chapter.md @@ -0,0 +1,1451 @@ + + +# L'épinglage + + + +Pour piloter les futures, ils doivent être épinglés en utilisant un type +spécial qui s'appelle `Pin`. Si vous lisez l'explication [du trait +`Future`][the `Future` trait] dans la [section précédente]["Executing `Future`s +and Tasks"], vous devriez constater la présence du `Pin` dans le +`self: Pin<&mut Self>` dans la définition de la méthode `Future::poll`. Mais +qu'est-ce que cela signifie, et pourquoi nous en avons besoin ? + + + +## Pourquoi épingler ? + + + +`Pin` fonctionne en binôme avec le marqueur `Unpin`. L'épinglage permet de +garantir qu'un objet qui implémente `!Unpin` ne sera jamais déplacé. Pour +comprendre pourquoi c'est nécessaire, nous devons nous rappeler comment `async` +et `await` fonctionnent. Imaginons le code suivant : + + + +```rust,edition2018,ignore +let premiere_future = /* ... */; +let seconde_future = /* ... */; +async move { + premiere_future.await; + seconde_future.await; +} +``` + + + +Sous le capot, cela crée un type anonyme qui implémente `Future`, ce qui va +fournir une méthode `poll` qui ressemble à ceci : + + + +```rust,ignore +// Le type `Future` généré pour notre bloc `async { ... }` +struct FutureAsynchrone { + premiere_future: FutOne, + seconde_future: FutTwo, + etat: Etat, +} + +// Liste des états dans lesquels notre bloc `async` peut être +enum Etat { + AttentePremiereFuture, + AttenteSecondeFuture, + Termine, +} + +impl Future for FutureAsynchrone { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + loop { + match self.etat { + Etat::AttentePremiereFuture => match self.premiere_future.poll(..) { + Poll::Ready(()) => self.etat = Etat::AttenteSecondeFuture, + Poll::Pending => return Poll::Pending, + } + Etat::AttenteSecondeFuture => match self.seconde_future.poll(..) { + Poll::Ready(()) => self.etat = Etat::Termine, + Poll::Pending => return Poll::Pending, + } + Etat::Termine => return Poll::Ready(()), + } + } + } +} +``` + + + +Lorsque `poll` est appelé la première fois, il va appeler `premiere_future`. Si +`premiere_future` ne peut pas être complété, `FutureAsynchrone::poll` va retourner +sa valeur. Les appels futurs à `poll` vont reprendre où le précédent s'est +arrêté. Ce fonctionnement va continuer jusqu'à ce que la future se termine au +complet. + + + +Cependant, que se passe-t-il si nous avons un bloc `async` qui utilise des +références ? Par exemple : + + + +```rust,edition2018,ignore +async { + let mut x = [0; 128]; + let lire_dans_un_tampon = lire_dans_un_tampon(&mut x); + lire_dans_un_tampon.await; + println!("{:?}", x); +} +``` + + + +Quelle structure va donner la compilation ? + + + +```rust,ignore +struct LireDansTampon<'a> { + tampon: &'a mut [u8], // cela pointe sur le `x` ci-desous +} + +struct FutureAsynchrone { + x: [u8; 128], + future_lire_dans_un_tampon: LireDansTampon<'quelle_duree_de_vie?>, +} +``` + + + +Ici, la future `LireDansTampon` contient une référence vers l'autre champ de +notre structure, `x`. Cependant, si `FutureAsynchrone` est déplacée, +l'emplacement de `x` va aussi être déplacé, ce qui va corrompre le pointeur +stocké dans `future_lire_dans_un_tampon.tampon`. + + + +L'épinglage des futures à un endroit précis de la mémoire évite ce problème, ce +qui va sécuriser la création de références vers des valeurs dans des blocs +`async`. + + + +## L'épinglage en détail + + + +Essayons de comprendre l'épinglage en utilisant un exemple légèrement plus +simple. Le problème que nous allons rencontrer ci-dessous peut se résumer à +notre manière de gérer les types auto-référentiels en Rust. + + + +Pour l'instant, notre exemple ressemble à ceci : + + + +```rust, ignore +#[derive(Debug)] +struct Test { + a: String, + b: *const String, +} + +impl Test { + fn new(texte: &str) -> Self { + Test { + a: String::from(texte), + b: std::ptr::null(), + } + } + + fn initialiser(&mut self) { + let self_ref: *const String = &self.a; + self.b = self_ref; + } + + fn a(&self) -> &str { + &self.a + } + + fn b(&self) -> &String { + assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); + unsafe { &*(self.b) } + } +} +``` + + + +`Test` propose des méthodes pour obtenir une référence vers la valeur des +champs `a` et `b`. Comme `b` est une référence vers `a`, nous le stockons comme +un pointeur puisque les règles d'emprunt de Rust ne nous autorisent pas à +définir cette durée de vie. Nous avons désormais ce que l'on appelle une +structure auto-référentielle. + + + +Notre exemple fonctionne bien si nous ne déplaçons aucune de nos données, comme +vous pouvez le constater en exécutant cet exemple : + + + +```rust +fn main() { + let mut test1 = Test::new("test1"); + test1.initialiser(); + let mut test2 = Test::new("test2"); + test2.initialiser(); + + println!("a: {}, b: {}", test1.a(), test1.b()); + println!("a: {}, b: {}", test2.a(), test2.b()); + +} +# #[derive(Debug)] +# struct Test { +# a: String, +# b: *const String, +# } +# +# impl Test { +# fn new(texte: &str) -> Self { +# Test { +# a: String::from(texte), +# b: std::ptr::null(), +# } +# } +# +# // We need an `init` method to actually set our self-reference +# fn initialiser(&mut self) { +# let self_ref: *const String = &self.a; +# self.b = self_ref; +# } +# +# fn a(&self) -> &str { +# &self.a +# } +# +# fn b(&self) -> &String { +# assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); +# unsafe { &*(self.b) } +# } +# } +``` + + + +Nous obtenons ce que nous attendions : + + + +```rust, ignore +a: test1, b: test1 +a: test2, b: test2 +``` + + + +Voyons maintenant ce qui se passe si nous permutions `test1` avec `test2` et +ainsi nous déplaçons les données : + + + +```rust +fn main() { + let mut test1 = Test::new("test1"); + test1.initialiser(); + let mut test2 = Test::new("test2"); + test2.initialiser(); + + println!("a: {}, b: {}", test1.a(), test1.b()); + std::mem::swap(&mut test1, &mut test2); + println!("a: {}, b: {}", test2.a(), test2.b()); + +} +# #[derive(Debug)] +# struct Test { +# a: String, +# b: *const String, +# } +# +# impl Test { +# fn new(texte: &str) -> Self { +# Test { +# a: String::from(texte), +# b: std::ptr::null(), +# } +# } +# +# fn initialiser(&mut self) { +# let self_ref: *const String = &self.a; +# self.b = self_ref; +# } +# +# fn a(&self) -> &str { +# &self.a +# } +# +# fn b(&self) -> &String { +# assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); +# unsafe { &*(self.b) } +# } +# } +``` + + + +Naïvement, nous pourrions penser que nous devrions obtenir l'écriture de +déboguage de `test1` deux fois comme ceci : + + + +```rust, ignore +a: test1, b: test1 +a: test1, b: test1 +``` + + + +Mais à la place, nous avons ceci : + + + +```rust, ignore +a: test1, b: test1 +a: test1, b: test2 +``` + + + +Le pointeur vers `test2.b` pointe toujours vers l'ancien emplacement qui est +maintenant `test1`. La structure n'est plus auto-référentielle, elle contient +un pointeur vers un champ dans un objet différent. Cela signifie que nous ne +pouvons plus considérer que la durée de vie de `test2.b` soit toujours liée à +la durée de vie de `test2`. + + + +Si vous n'êtes pas convaincu, ceci devrait vous convaincre : + + + +```rust +fn main() { + let mut test1 = Test::new("test1"); + test1.initialiser(); + let mut test2 = Test::new("test2"); + test2.initialiser(); + + println!("a: {}, b: {}", test1.a(), test1.b()); + std::mem::swap(&mut test1, &mut test2); + test1.a = "J'ai complètement changé, désormais !".to_string(); + println!("a: {}, b: {}", test2.a(), test2.b()); + +} +# #[derive(Debug)] +# struct Test { +# a: String, +# b: *const String, +# } +# +# impl Test { +# fn new(texte: &str) -> Self { +# Test { +# a: String::from(texte), +# b: std::ptr::null(), +# } +# } +# +# fn initialiser(&mut self) { +# let self_ref: *const String = &self.a; +# self.b = self_ref; +# } +# +# fn a(&self) -> &str { +# &self.a +# } +# +# fn b(&self) -> &String { +# assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); +# unsafe { &*(self.b) } +# } +# } +``` + + + +Le schéma ci-dessous peut vous aider à voir ce qui se passe : + + + +**Figure 1 : avant et après l'échange** +![probleme_echange](../assets/swap_problem.jpg) + + + +C'est ainsi facile d'avoir un fonctionnement indéfini et aussi de provoquer une +autre défaillance spectaculaire. + + + +## L'épinglage dans la pratique + + + +Voyons voir comment l'épinglage et le type `Pin` peut nous aider à résoudre ce +problème. + + + +Le type `Pin` enveloppe les types de pointeurs, ce qui garantit que les valeurs +derrière ce pointeur ne seront pas déplacées. Par exemple, `Pin<&mut T>`, +`Pin<&T>`, `Pin>` garantissent tous que `T` ne sera pas déplacé même si +`T: !Unpin`. + + + +La plupart des types n'ont pas de problème lorsqu'ils sont déplacés. Ces types +implémentent le trait `Unpin`. Les pointeurs vers des types `Unpin` peuvent +être librement logés à l'intérieur d'un `Pin`, ou en être retiré. Par exemple, +`u8` implémente `Unpin`, donc `Pin<&mut u8>` se comporte exactement comme un +`&mut u8` normal. + + + +Cependant, les types qui ne peuvent pas être déplacés après avoir été épinglés +ont un marqueur `!Unpin`. Les futures créées par `async` et `await` en sont un +exemple. + + + +### L'épinglage sur la pile + + + +Retournons à notre exemple. Nous pouvons résoudre notre problème en utilisant +`Pin`. Voyons ce à quoi notre exemple ressemblerait si nous avions utilisé un +pointeur épinglé à la place : + + + + + +```rust, ignore +use std::pin::Pin; +use std::marker::PhantomPinned; + +#[derive(Debug)] +struct Test { + a: String, + b: *const String, + _marqueur: PhantomPinned, +} + +impl Test { + fn new(texte: &str) -> Self { + Test { + a: String::from(texte), + b: std::ptr::null(), + _marqueur: PhantomPinned, // Cela rends notre type `!Unpin` + } + } + + fn initialiser(self: Pin<&mut Self>) { + let self_pointeur: *const String = &self.a; + let this = unsafe { self.get_unchecked_mut() }; + this.b = self_pointeur; + } + + fn a(self: Pin<&Self>) -> &str { + &self.get_ref().a + } + + fn b(self: Pin<&Self>) -> &String { + assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); + unsafe { &*(self.b) } + } +} +``` + + + +L'épinglage d'un objet à la pile va toujours être `unsafe` si notre type +implémente `!Unpin`. Vous pouvez utiliser une crate comme +[`pin_utils`][pin_utils] pour éviter d'avoir à écrire notre propre `unsafe` code +lorsqu'on épinglera sur la pile. + + + +Ci-dessous, nous épinglons les objets `test1` et `test2` sur la pile : + + + +```rust +pub fn main() { + // test1 peut être déplacé en sécurité avant que nous l'initialisions : + let mut test1 = Test::new("test1"); + // Notez que nous masquons `test1` pour l'empêcher d'être toujours + // accessible : + let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; + Test::initialiser(test1.as_mut()); + + let mut test2 = Test::new("test2"); + let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; + Test::initialiser(test2.as_mut()); + + println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref())); + println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref())); +} +# use std::pin::Pin; +# use std::marker::PhantomPinned; +# +# #[derive(Debug)] +# struct Test { +# a: String, +# b: *const String, +# _marqueur: PhantomPinned, +# } +# +# +# impl Test { +# fn new(texte: &str) -> Self { +# Test { +# a: String::from(texte), +# b: std::ptr::null(), +# // Cela rends notre type `!Unpin` +# _marqueur: PhantomPinned, +# } +# } +# +# fn initialiser(self: Pin<&mut Self>) { +# let self_pointeur: *const String = &self.a; +# let this = unsafe { self.get_unchecked_mut() }; +# this.b = self_pointeur; +# } +# +# fn a(self: Pin<&Self>) -> &str { +# &self.get_ref().a +# } +# +# fn b(self: Pin<&Self>) -> &String { +# assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); +# unsafe { &*(self.b) } +# } +# } +``` + + + +Maintenant, si nous essayons de déplacer nos données, nous avons désormais une +erreur de compilation : + + + +```rust, compile_fail +pub fn main() { + let mut test1 = Test::new("test1"); + let mut test1 = unsafe { Pin::new_unchecked(&mut test1) }; + Test::initialiser(test1.as_mut()); + + let mut test2 = Test::new("test2"); + let mut test2 = unsafe { Pin::new_unchecked(&mut test2) }; + Test::initialiser(test2.as_mut()); + + println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref())); + std::mem::swap(test1.get_mut(), test2.get_mut()); + println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref())); +} +# use std::pin::Pin; +# use std::marker::PhantomPinned; +# +# #[derive(Debug)] +# struct Test { +# a: String, +# b: *const String, +# _marqueur: PhantomPinned, +# } +# +# +# impl Test { +# fn new(txt: &str) -> Self { +# Test { +# a: String::from(txt), +# b: std::ptr::null(), +# _marqueur: PhantomPinned, // Cela rends notre type `!Unpin` +# } +# } +# +# fn initialiser(self: Pin<&mut Self>) { +# let self_pointeur: *const String = &self.a; +# let this = unsafe { self.get_unchecked_mut() }; +# this.b = self_pointeur; +# } +# +# fn a(self: Pin<&Self>) -> &str { +# &self.get_ref().a +# } +# +# fn b(self: Pin<&Self>) -> &String { +# assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); +# unsafe { &*(self.b) } +# } +# } +``` + + + +Le système de type nous empêche de déplacer les données. + + + +> Il est important que vous compreniez que l'épinglage sur la pile s'appuie +> toujours sur les garanties que vous écrivez dans votre `unsafe`. Même si nous +> savons que ce sur quoi pointe le `&'a mut T` est épinglé pour la durée de vie +> de `'a`, nous ne pouvons pas savoir si la donnée sur laquelle pointe +> `&'a mut T` n'est pas déplacée après que `'a` soit terminé. Si c'est ce qui +> se passe, cela violera le contrat du `Pin`. +> +> Une erreur courante est d'oublier de masquer la variable originale alors que +> vous pourriez terminer le `Pin` et déplacer la donnée après le `&'a mut T` +> comme nous le montrons ci-dessous (ce qui viole le contrat du `Pin`) : +> +> ```rust +> fn main() { +> let mut test1 = Test::new("test1"); +> let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) }; +> Test::init(test1_pin.as_mut()); +> +> drop(test1_pin); +> println!(r#"test1.b pointe sur "test1": {:?}..."#, test1.b); +> +> let mut test2 = Test::new("test2"); +> mem::swap(&mut test1, &mut test2); +> println!("... et maintenant il pointe nulle part : {:?}", test1.b); +> } +> # use std::pin::Pin; +> # use std::marker::PhantomPinned; +> # use std::mem; +> # +> # #[derive(Debug)] +> # struct Test { +> # a: String, +> # b: *const String, +> # _marqueur: PhantomPinned, +> # } +> # +> # +> # impl Test { +> # fn new(txt: &str) -> Self { +> # Test { +> # a: String::from(txt), +> # b: std::ptr::null(), +> # // Cela rends notre type `!Unpin` +> # _marqueur: PhantomPinned, +> # } +> # } +> # +> # fn init<'a>(self: Pin<&'a mut Self>) { +> # let self_pointeur: *const String = &self.a; +> # let this = unsafe { self.get_unchecked_mut() }; +> # this.b = self_pointeur; +> # } +> # +> # fn a<'a>(self: Pin<&'a Self>) -> &'a str { +> # &self.get_ref().a +> # } +> # +> # fn b<'a>(self: Pin<&'a Self>) -> &'a String { +> # assert!(!self.b.is_null(), "Test::b est appelé sans appeler avant Test::initialiser"); +> # unsafe { &*(self.b) } +> # } +> # } +> ``` + + + +### Epingler sur le tas + + + +L'épinglage d'un type `!Unpin` sur le tas donne une adresse stable à vos +données donc nous savons que la donnée sur laquelle nous pointons ne peut pas +être déplacée après avoir été épinglée. Contrairement à l'épinglage sur la +pile, nous savons que la donnée va être épinglée pendant la durée de vie de +l'objet. + + + +```rust, edition2018 +use std::pin::Pin; +use std::marker::PhantomPinned; + +#[derive(Debug)] +struct Test { + a: String, + b: *const String, + _marqueur: PhantomPinned, +} + +impl Test { + fn new(texte: &str) -> Pin> { + let t = Test { + a: String::from(texte), + b: std::ptr::null(), + _marqueur: PhantomPinned, + }; + let mut boxed = Box::pin(t); + let self_pointeur: *const String = &boxed.as_ref().a; + unsafe { boxed.as_mut().get_unchecked_mut().b = self_pointeur }; + + boxed + } + + fn a(self: Pin<&Self>) -> &str { + &self.get_ref().a + } + + fn b(self: Pin<&Self>) -> &String { + unsafe { &*(self.b) } + } +} + +pub fn main() { + let test1 = Test::new("test1"); + let test2 = Test::new("test2"); + + println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b()); + println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b()); +} +``` + + + +Certaines fonctions nécessitent que les futures avec lesquelles elles +fonctionnent soient des `Unpin`. Pour utiliser une `Future` ou un `Stream` qui +n'est pas `Unpin` avec une fonction qui nécessite des types `Unpin`, vous devez +d'abord épingler la valeur en utilisant soit `Box::pin` (pour créer un +`Pin>`) ou la macro `pin_utils::pin_mut!` (pour créer une +`Pin<&mut T>`). `Pin>` et `Pin<&mut Future>` peuvent tous deux être +utilisés comme des `Future`s, et les deux implémentent `Unpin`. + + + +Par exemple : + + + +```rust,edition2018,ignore +use pin_utils::pin_mut; // `pin_utils` est une crate bien pratique, + // disponible sur crates.io + +// Une fonction qui prend en argument une `Future` qui implémente `Unpin`. +fn executer_une_future_unpin(x: impl Future + Unpin) { /* ... */ } + +let future = async { /* ... */ }; +executer_une_future_unpin(future); // Erreur : `future` n'implémente pas + // le trait `Unpin` + +// Epingler avec `Box`: +let future = async { /* ... */ }; +let future = Box::pin(future); +executer_une_future_unpin(future); // OK + +// Epingler avec `pin_mut!`: +let future = async { /* ... */ }; +pin_mut!(future); +executer_une_future_unpin(future); // OK +``` + + + +## En résumé + + + +1. Si `T: Unpin` (ce qu'il est par défaut), alors `Pin<'a, T>` est strictement +équivalent à `&'a mut T`. Autrement dit : `Unpin` signifie que ce type peut +être déplacé sans problème même lorsqu'il est épinglé, donc `Pin` n'aura pas +d'impact sur ce genre de type. + +2. Obtenir un `&mut T` à partir d'un T épinglé nécessite du code non sécurisé +si `T: !Unpin`. + +3. La plupart des bibliothèques standard implémentent `Unpin`. C'est la même +chose pour la plupart des types "normaux" que vous utilisez en Rust. Une +`Future` générée par `async` et `await` est une exception à cette généralité. + +4. Vous pouvez ajouter un lien `!Unpin` sur un type avec la version +expérimentale de Rust avec un drapeau de fonctionnalité, ou en ajoutant le +`std::marker::PhantomPinned` sur votre type avec la version stable. + +5. Vous pouvez épingler des données soit sur la pile, soit sur le tas. + +6. Epingler un objet `!Unpin` sur la pile nécessite `unsafe` + +7. Epingler un objet `!Unpin` sur le tas ne nécessite pas `unsafe`. Il existe +un raccourci pour faire ceci avec `Box::pin`. + +8. Pour les données épinglées où `T: !Unpin`, vous devez maintenir l'invariant +dont sa mémoire n'est pas invalidée ou réaffectée _à partir du moment où elle +est épinglée jusqu'à l'appel à drop_. C'est une partie très importante du +_contrat d'épinglage_. + + + +["Executing `Future`s and Tasks"]: ../02_execution/01_chapter.md +[the `Future` trait]: ../02_execution/02_future.md +[pin_utils]: https://docs.rs/pin-utils/ diff --git a/FRENCH/src/SUMMARY.md b/FRENCH/src/SUMMARY.md index beeca743..bf6d1f99 100644 --- a/FRENCH/src/SUMMARY.md +++ b/FRENCH/src/SUMMARY.md @@ -1,3 +1,5 @@ # Table des matières +- [L'épinglage](04_pinning/01_chapter.md) + [Traduction des termes](translation-terms.md) diff --git a/FRENCH/src/assets/swap_problem.jpg b/FRENCH/src/assets/swap_problem.jpg new file mode 100644 index 00000000..9391bff0 Binary files /dev/null and b/FRENCH/src/assets/swap_problem.jpg differ diff --git a/FRENCH/src/translation-terms.md b/FRENCH/src/translation-terms.md index 0917f018..0874777b 100644 --- a/FRENCH/src/translation-terms.md +++ b/FRENCH/src/translation-terms.md @@ -5,3 +5,9 @@ français. | Anglais | Français | Remarques | | ------- | ------ | ------ | +| heap | tas | - | +| nightly Rust | version expérimentale de Rust | - | +| pin | épingler | - | +| shadow | masquer | remplacer une variable par une autre de même nom | +| stack | pile | - | +| unsage | non sécurisé | - |