diff --git a/FRENCH/src/SUMMARY.md b/FRENCH/src/SUMMARY.md index 4305682b80..95e39c60b0 100644 --- a/FRENCH/src/SUMMARY.md +++ b/FRENCH/src/SUMMARY.md @@ -24,6 +24,7 @@ - [Comprendre la possession](ch04-00-understanding-ownership.md) - [Qu'est-ce que la possession ?](ch04-01-what-is-ownership.md) - [Les références et l'emprunt](ch04-02-references-and-borrowing.md) + - [Le type slice](ch04-03-slices.md) - [Utiliser les structures pour structurer des données apparentées](ch05-00-structs.md) - [Définir et instancier des structures](ch05-01-defining-structs.md) diff --git a/FRENCH/src/ch04-03-slices.md b/FRENCH/src/ch04-03-slices.md new file mode 100644 index 0000000000..3a93bfeaf2 --- /dev/null +++ b/FRENCH/src/ch04-03-slices.md @@ -0,0 +1,936 @@ + + +## Le type slice + + + +Un autre type de donnée qui ne prend pas possession est la *slice*. Une slice +vous permet d'obtenir une référence vers une séquence continue d'éléments d'une +collection plutôt que toute la collection. + + + +Voici un petit problème de programmation : écrire une fonction qui prend une +chaîne de caractères et retourne le premier mot qu'elle trouve dans cette +chaîne. Si la fonction ne trouve pas d'espace dans la chaîne, cela veut dire +que la chaîne est en un seul mot, donc la chaîne en entier doit être retournée. + + + +Imaginons la signature de cette fonction : + + + +```rust,ignore +fn premier_mot(s: &String) -> ? +``` + + + +Cette fonction, `premier_mot`, prend un `&String` comme paramètre. Nous ne +voulons pas en prendre possession, donc c'est ce qu'il nous faut. Mais que +devons-nous retourner ? Nous n'avons aucun moyen de désigner une *partie* +d'une chaîne de caractères. Cependant, nous pouvons retourner l'indice de la +fin du mot. Essayons cela, dans l'encart 4-7 : + + + +Fichier : src/main.rs + + + +```rust +fn premier_mot(s: &String) -> usize { + let octets = s.as_bytes(); + + for (i, &element) in octets.iter().enumerate() { + if element == b' ' { + return i; + } + } + + s.len() +} +``` + + + +Encart 4-7 : La fonction `premier_mot` qui retourne +l'indice d'un octet provenant du paramètre `String` + + + +Comme nous avons besoin de parcourir la `String` élément par élément et de +vérifier si la valeur est une espace, nous convertissons notre `String` en un +tableau d'octets en utilisant la méthode `as_bytes` : + + + +```rust,ignore +let octets = s.as_bytes(); +``` + + + +Ensuite, nous créons un itérateur sur le tableau d'octets en utilisant la +méthode `iter` : + + + +```rust,ignore +for (i, &element) in octets.iter().enumerate() { +``` + + + +Nous aborderons plus en détail les itérateurs dans le chapitre 13. Pour le +moment, sachez que `iter` est une méthode qui retourne chaque élément d'une +collection, et que `enumerate` transforme le résultat de `iter` pour retourner +plutôt chaque élément comme un tuple. Le premier élément du tuple retourné par +`enumerate` est l'indice, et le second élément est une référence vers l'élément. +C'est un peu plus pratique que de calculer les indices par nous-mêmes. + + + +Comme la méthode `enumerate` retourne un tuple, nous pouvons utiliser des motifs +pour déstructurer ce tuple, comme nous pourrions le faire n'importe où avec +Rust. Donc dans la boucle `for`, nous précisons un motif qui indique que nous +définissons `i` pour l'indice au sein du tuple et `&element` pour l'octet dans +le tuple. Comme nous obtenons une référence vers l'élément avec +`.iter().enumerate()`, nous utilisons `&` dans le motif. + + + +Au sein de la boucle `for`, nous recherchons l'octet qui représente l'espace en +utilisant la syntaxe de littéral d'octet. Si nous trouvons une espace, nous +retournons sa position. Sinon, nous retournons la taille de la chaîne en +utilisant `s.len()` : + + + +```rust,ignore + if element == b' ' { + return i; + } +} + +s.len() +``` + + + +Nous avons maintenant une façon de trouver l'indice de la fin du premier mot +dans la chaîne de caractères, mais il y a un problème. Nous retournons un +`usize` tout seul, mais il n'a du sens que lorsqu'il est lié au `&String`. +Autrement dit, comme il a une valeur séparée de la `String`, il n'y a pas de +garantie qu'il restera toujours valide dans le futur. Imaginons le programme +dans l'encart 4-8 qui utilise la fonction `premier_mot` de l'encart 4-7 : + + + +Fichier : src/main.rs + + + +```rust +# fn premier_mot(s: &String) -> usize { +# let octets = s.as_bytes(); +# +# for (i, &element) in octets.iter().enumerate() { +# if element == b' ' { +# return i; +# } +# } +# +# s.len() +# } +# +fn main() { + let mut s = String::from("hello world"); + + let mot = premier_mot(&s); // la variable mot aura 5 comme valeur. + + s.clear(); // ceci vide la String, elle vaut maintenant "". + + // mot a toujours la valeur 5 ici, mais il n'y a plus de chaîne qui donne + // du sens à la valeur 5. mot est maintenant complètement invalide ! +} +``` + + + +Encart 4-8 : On stocke le résultat de l'appel à la +fonction `premier_mot` et ensuite on change le contenu de la `String` + + + +Ce programme se compile sans aucune erreur et le ferait toujours si nous +utilisions `mot` après avoir appelé `s.clear()`. Comme `mot` n'est pas du tout +lié à `s`, `mot` contient toujours la valeur `5`. Nous pourrions utiliser cette +valeur `5` avec la variable `s` pour essayer d'en extraire le premier mot, mais +cela serait un bogue, car le contenu de `s` a changé depuis que nous avons +enregistré `5` dans `mot`. + + + +Se préoccuper en permanence que l'indice présent dans `mot` ne soit plus +synchronisé avec les données présentes dans `s` est fastidieux et source +d'erreur ! La gestion de ces indices est encore plus risquée si nous écrivons +une fonction `second_mot`. Sa signature ressemblerait à ceci : + + + +```rust,ignore +fn second_mot(s: &String) -> (usize, usize) { +``` + + + +Maintenant, nous avons un indice de début *et* un indice de fin, donc nous avons +encore plus de valeurs qui sont calculées à partir d'une donnée dans un état +donné, mais qui ne sont pas liées du tout à l'état de cette donnée. Nous avons +maintenant trois variables isolées qui ont besoin d'être maintenues à jour. + + + +Heureusement, Rust a une solution pour ce problème : les *slices* de chaînes de +caractères. + + + +### Les slices de chaînes de caractères + + + +Une *slice de chaîne de caractères* (ou *slice de chaîne*) est une référence à +une partie d'une `String`, et ressemble à ceci : + +```rust +let s = String::from("hello world"); + +let hello = &s[0..5]; +let world = &s[6..11]; +``` + + + +Cela ressemble à une référence pour toute la `String`, mais avec la partie +`[0..5]` en plus. Plutôt que d'être une référence vers toute la `String`, c'est +une référence vers une partie de la `String`. + + + +Nous pouvons créer des slices en utilisant un intervalle entre crochets en +spécifiant `[indice_debut..indice_fin]`, où `indice_debut` est la position du +premier octet de la slice et `indice_fin` est la position juste après le dernier +octet de la slice. En interne, la structure de données de la slice stocke la +position de départ et la longueur de la slice, ce qui correspond à `indice_fin` +moins `indice_debut`. Donc dans le cas de `let world = &s[6..11];`, `world` est +une slice qui contient un pointeur vers l'octet d'indice 6 de `s` et une +longueur de 5. + + + +L'illustration 4-6 montre ceci dans un schéma. + + + + + +world contient un pointeur vers l'octet d'indice 6 de la String s et
+une longueur de 5 + + + +Illustration 4-6 : Une slice de chaîne qui pointe vers +une partie d'une `String` + + + +Avec la syntaxe d'intervalle `..` de Rust, si vous voulez commencer au premier +indice (zéro), vous pouvez ne rien mettre avant les deux points. Autrement dit, +ces deux cas sont identiques : + +```rust +let s = String::from("hello"); + +let slice = &s[0..2]; +let slice = &s[..2]; +``` + + + +De la même manière, si votre slice contient le dernier octet de la `String`, +vous pouvez ne rien mettre à la fin. Cela veut dire que ces deux cas sont +identiques : + + + +```rust +let s = String::from("hello"); + +let taille = s.len(); + +let slice = &s[3..taille]; +let slice = &s[3..]; +``` + + + +Vous pouvez aussi ne mettre aucune limite pour créer une slice de toute la +chaîne de caractères. Ces deux cas sont donc identiques : + + + +```rust +let s = String::from("hello"); + +let taille = s.len(); + +let slice = &s[0..taille]; +let slice = &s[..]; +``` + + + +> Remarque : Les indices de l'intervalle d'une slice de chaîne doivent toujours +> se trouver dans les zones acceptables de séparation des caractères encodés en +> UTF-8. Si vous essayez de créer une slice de chaîne qui s'arrête au milieu +> d'un caractère encodé sur plusieurs octets, votre programme va se fermer avec +> une erreur. Afin de simplifier l'explication des slices de chaînes, nous +> utiliserons uniquement l'ASCII dans cette section ; nous verrons la gestion +> d'UTF-8 dans la section [“Stocker du texte encodé en UTF-8 avec les chaînes de +> caractères”][strings] du chapitre 8. + + + +Maintenant que nous savons tout cela, essayons de réécrire `premier_mot` pour +qu'il retourne une slice. Le type pour les slices de chaînes de caractères +s'écrit `&str` : + + + +Fichier : src/main.rs + + + +```rust +fn premier_mot(s: &String) -> &str { + let octets = s.as_bytes(); + + for (i, &element) in octets.iter().enumerate() { + if element == b' ' { + return &s[0..i]; + } + } + + &s[..] +} +``` + + + +Nous récupérons l'indice de la fin du mot de la même façon que nous l'avions +fait dans l'encart 4-7, en cherchant la première occurrence d'une espace. +Lorsque nous trouvons une espace, nous retournons une slice de chaîne en +utilisant le début de la chaîne de caractères et l'indice de l'espace comme +indices de début et de fin respectivement. + + + +Désormais, quand nous appelons `premier_mot`, nous récupérons une unique valeur +qui est liée à la donnée de base. La valeur se compose d'une référence vers le +point de départ de la slice et du nombre d'éléments dans la slice. + + + +Retourner une slice fonctionnerait aussi pour une fonction `second_mot` : + + + +```rust,ignore +fn second_mot(s: &String) -> &str { +``` + + + +Nous avons maintenant une API simple qui est bien plus difficile à mal utiliser, +puisque le compilateur va s'assurer que les références dans la `String` seront +toujours en vigueur. Vous souvenez-vous du bogue du programme de l'encart 4-8, +lorsque nous avions un indice vers la fin du premier mot mais qu'ensuite nous +avions vidé la chaîne de caractères et que notre indice n'était plus valide ? Ce +code était logiquement incorrect, mais ne montrait pas immédiatement une erreur. +Les problèmes apparaîtront plus tard si nous essayons d'utiliser l'indice du +premier mot avec une chaîne de caractères qui a été vidée. Les slices rendent ce +bogue impossible et nous signalent bien plus tôt que nous avons un problème avec +notre code. Utiliser la version avec la slice de `premier_mot` va causer une +erreur de compilation : + + + +Fichier : src/main.rs + + + +```rust,ignore,does_not_compile +fn main() { + let mut s = String::from("hello world"); + + let mot = premier_mot(&s); + + s.clear(); // Erreur ! + + println!("Le premier mot est : {}", mot); +} +``` + + + +Voici l'erreur du compilateur : + + + +```text +error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable + --> src/main.rs:18:5 + | +16 | let mot = premier_mot(&s); + | -- immutable borrow occurs here +17 | +18 | s.clear(); // Erreur ! + | ^^^^^^^^^ mutable borrow occurs here +19 | +20 | println!("Le premier mot est : {}", mot); + | --- immutable borrow later used here +``` + + + +Rappelons-nous que d'après les règles d'emprunt, si nous avons une référence +immuable vers quelque chose, nous ne pouvons pas avoir une référence mutable +en même temps. Étant donné que `clear` a besoin de modifier la `String`, il a +besoin d'une référence mutable. Rust interdit cette situation, et la compilation +échoue. Non seulement Rust a simplifié l'utilisation de notre API, mais il a +aussi éliminé une catégorie entière d'erreurs au moment de la compilation ! + + + +#### Les littéraux de chaîne de caractères sont aussi des slices + + + +Rappelez-vous lorsque nous avons appris que les littéraux de chaîne de +caractères étaient enregistrés dans le binaire. Maintenant que nous connaissons +les slices, nous pouvons désormais comprendre les littéraux de chaîne. + +```rust +let s = "Hello, world!"; +``` + + + +Ici, le type de `s` est un `&str` : c'est une slice qui pointe vers un endroit +précis du binaire. C'est aussi la raison pour laquelle les littéraux de chaîne +sont immuables ; `&str` est une référence immuable. + + + +#### Les slices de chaînes de caractères en paramètres + + + +Savoir que l'on peut utiliser des slices de littéraux et de `String` nous incite +à apporter une petite amélioration à `premier_mot`, dont voici la signature : + + + +```rust,ignore +fn premier_mot(s: &String) -> &str { +``` + + + +Un Rustacé plus expérimenté écrirait plutôt la signature de l'encart 4-9, car +cela nous permet d'utiliser la même fonction sur les `&String` et aussi les +`&str` : + + + +```rust,ignore +fn premier_mot(s: &str) -> &str { +``` + + + +Encart 4-9 : Amélioration de la fonction `premier_mot` en +utilisant une slice de chaîne de caractères comme type du paramètre `s` + + + +Si nous avons une slice de chaîne, nous pouvons la passer en argument +directement. Si nous avons une `String`, nous pouvons envoyer une slice de toute +la `String`. Définir une fonction qui prend une slice de chaîne plutôt qu'une +référence à une `String` rend notre API plus générique et plus utile sans perdre +aucune fonctionnalité : + + + +Fichier : src/main.rs + + + +```rust +# fn premier_mot(s: &str) -> &str { +# let octets = s.as_bytes(); +# +# for (i, &element) in octets.iter().enumerate() { +# if element == b' ' { +# return &s[0..i]; +# } +# } +# +# &s[..] +# } +fn main() { + let ma_string = String::from("hello world"); + + // premier_mot fonctionne avec les slices de `String` + let mot = premier_mot(&ma_string[..]); + + let mon_litteral_de_chaine = "hello world"; + + // premier_mot fonctionne avec les slices de littéraux de chaîne + let mot = premier_mot(&mon_litteral_de_chaine[..]); + + // Comme les littéraux de chaîne *sont* déjà des slices de chaînes, + // cela fonctionne aussi, sans la syntaxe de slice ! + let mot = premier_mot(mon_litteral_de_chaine); +} +``` + + + +### Les autres slices + + + +Les slices de chaînes de caractères, comme vous pouvez l'imaginer, sont +spécifiques aux chaînes de caractères. Mais il existe aussi un type de slice +plus générique. Imaginons ce tableau de données : + +```rust +let a = [1, 2, 3, 4, 5]; +``` + + + +Tout comme nous pouvons nous référer à une partie d'une chaîne de caractères, +nous pouvons nous référer à une partie d'un tableau. Nous pouvons le faire comme +ceci : + +```rust +let a = [1, 2, 3, 4, 5]; + +let slice = &a[1..3]; +``` + + + +Cette slice est de type `&[i32]`. Elle fonctionne de la même manière que les +slices de chaînes de caractères, en enregistrant une référence vers le premier +élément et une longueur. Vous utiliserez ce type de slice pour tous les autres +types de collections. Nous aborderons ces collections en détail quand nous +verrons les vecteurs au chapitre 8. + + + +## Résumé + + + +Les concepts de possession, d'emprunt et de slices garantissent la sécurité de +la mémoire dans les programmes Rust au moment de la compilation. Le langage Rust +vous donne le contrôle sur l'utilisation de la mémoire comme tous les autres +langages de programmation système, mais le fait que celui qui possède des +données nettoie automatiquement ces données quand il sort de la portée vous +permet de ne pas avoir à écrire et déboguer du code en plus pour avoir cette +fonctionnalité. + + + +La possession influe sur de nombreuses autres fonctionnalités de Rust, c'est +pourquoi nous allons encore parler de ces concepts plus loin dans le livre. +Passons maintenant au chapitre 5 et découvrons comment regrouper des données +ensemble dans une `struct`. + + + +[strings]: ch08-02-strings.html