diff --git a/PLANNING.md b/PLANNING.md index cd50040d29f83ead6e6440fdf9f44c0e057403da..09505ee41343b099f9b37e035cddb160abff3e62 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -10,20 +10,19 @@ - [] Lundi après-midi (13h-14h15, 14h45-16h): - part02 - [] Mardi matin (9h-10h15, 10h45-12h): - - part03 + - part03-part04 - [] Mardi après-midi (13h-14h15, 14h45-16h): - - part04 + - part05-part06 - [] Mercredi matin (9h-10h15, 10h45-12h): - - part05, part06 -- [] Mercredi après-midi (13h-14h15, 14h45-16h): - Gestion d'erreurs - - Lambda + - Closures +- [] Mercredi après-midi (13h-14h15, 14h45-16h): + - Collections (Vec, String), Exos - [] Jeudi matin (9h-10h15, 10h45-12h): - - Collections (Vec, String, HashMap) - Itérateurs + - Lifetimes - [] Jeudi après-midi (13h-14h15, 14h45-16h): - CLI - - Lifetimes - [] Vendredi matin (9h-10h15, 10h45-12h): - Unsafe Rust - [] Vendredi après-midi (13h-14h15, 14h45-16h): diff --git a/book/book.toml b/book/book.toml index 78ab4a0e5be49a0c31d47f9e83483f922c1b1294..767af31f2e9bf7eec1efa6f47bd121ed1e1efb36 100644 --- a/book/book.toml +++ b/book/book.toml @@ -5,6 +5,9 @@ multilingual = false src = "src" title = "Rust-101: Université d'été" +[output.html] +mathjax-support = true + [output.html.playground] editable = true diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 718d00bd83710e40deada98bb6803df45e8ca210..fca73a34ce77640a683d62ecac10e6a72fe38217 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,10 +9,10 @@ - [La propriété](./part04.md) - [Modules et visibilité](./part05.md) - [Les petits trucs sympas qui aident au développement](./part06.md) -- [Part 07](./part07.md) -- [Part 08](./part08.md) -- [Part 09](./part09.md) -- [Part 10](./part10.md) -- [Part 11](./part11.md) +- [La gestion des erreurs en Rust](./part07.md) +- [Les closures](./part08.md) +- [Les itérateurs](./part10.md) +- [Les collections](./collections.md) +- [Lifetimes](./lifetimes.md) - [CLI](./cli.md) -- [Part 12](./part12.md) +- [Unsafe](./part12.md) diff --git a/book/src/cli.md b/book/src/cli.md index 80d0a41f10a7b44a371bac5f71ce51066c228f19..20c22f4d3de02577983a029f69f7bc023beac262 100644 --- a/book/src/cli.md +++ b/book/src/cli.md @@ -4,9 +4,16 @@ Les concepts abordés dans cet exemple sont: -1. [L'interface en ligne de commande et l'utilisation de librairies externes.](#linterface-à-la-ligne-de-commande-et-lutilisation-de-librairies-externes) -2. [Les entrées / sorties.](#les-entrées--sorties) -3. Une gestion des erreurs plus ergonomique. +- [Interface en ligne de commande et entrées / sorties](#interface-en-ligne-de-commande-et-entrées--sorties) + - [Concepts](#concepts) + - [Discussion](#discussion) + - [L'interface à la ligne de commande et l'utilisation de librairies externes](#linterface-à-la-ligne-de-commande-et-lutilisation-de-librairies-externes) + - [Le `builder` pattern](#le-builder-pattern) + - [Gestion d'erreur un peu simplifiée](#gestion-derreur-un-peu-simplifiée) + - [Le `derive` pattern](#le-derive-pattern) + - [Les entrées / sorties](#les-entrées--sorties) + - [Lecture de fichier](#lecture-de-fichier) + - [Écriture dans un fichier](#écriture-dans-un-fichier) ## Discussion diff --git a/book/src/collections.md b/book/src/collections.md new file mode 100644 index 0000000000000000000000000000000000000000..cd901fc2127dd34bc0dec7d50087dd56df41169e --- /dev/null +++ b/book/src/collections.md @@ -0,0 +1,194 @@ +# Les collections + +## Concepts + +Les concepts abordés dans cet exemple sont: + +- [Les collections](#les-collections) + - [Concepts](#concepts) + - [Documentation](#documentation) + - [Discussion](#discussion) + - [Le type `Vec`](#le-type-vect) + - [Le type `String`](#le-type-string) + - [Les slices](#les-slices) + +## Documentation + +Afin de compléter ce cours, je vous recommande la lecture des ressources suivantes : + +- [Les Vec](https://doc.rust-lang.org/book/ch08-01-vectors.html) +- [Les String](https://doc.rust-lang.org/book/ch08-02-strings.html) +- [Les slices](https://doc.rust-lang.org/book/ch04-03-slices.html) + +## Discussion + +En Rust, comme dans la plupart des langages modernes, il existe des structures de données qui permettent +de se simplifier la vie lors de l'implémentation de divers algorithmes. + +Dans ce chapitre, nous allons discuter des types `Vec<T>`, `String`, des slices. + +Dans ce code nous modifions que très peu le code [de la partie 6](part06.md) afin +d'utiliser les types `Vec<i32>`, `String` et les slices. + +### Le type `Vec<T>` + +Le type `Vec<T>` est une collection qui permet de stocker des données d'un type unique générique, `T`. +Ces données sont stockées dans un espace mémoire qui est garanti d'être contigu. Cet espace mémoire +peut changer dynamiquement à l'exécution contrairement aux tableaux statiques. D'un certain point +de vue, on peut le considérer comme un "pointeur intelligent": un `Vec` est un pointeur sur le tas, +qui a une certaine capacité mémoire et sait combien la mémoire sur laquelle il pointe +est pleine. + +Dans notre code, nous avons créé une fonction `read_command_line()` +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:read_command_line}} +``` +Ici, un `Vec<i32>` est instancié et on ajoute des éléments aléatoires dedans à l'aide de la +crate `rand()`. +Pour créer un `Vec` vide, on utilise la fonction associée, qui crée un vecteur vide +```rust +{{#include ../../codes/rust_lang/collections/src/io.rs:vec_new}} +``` +Ensuite, on remplit le vecteur avec des nombres aléatoires à l'aide +d'une boucle `for` +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:vec_for}} +``` +qui itère sur les indices allant de `0` à `len-1`. +Comme, nous n'utilisons pas l'indice, nous l'ignorons à l'aide de l'annotation `_i`. +On ajoute des éléments dans le vecteur avec la fonction `push()` et comme nous modifions +`v` il est primordial de noter qu'il est `mut`-able. Le générateur de nombre aléatoire +est stocké dans une variable `rng` qui doit elle aussi être mutable. +En effet, les générateurs de nombres aléatoires stockent en général un état interne. + +### Le type `String` + +Une `String` ou un chaîne de caractère, est une séquence de caractères `UTF-8`. +On pourrait penser naïvement que ce n'est rien d'autre qu'un `Vec<char>`: en fait c'est plus compliqué que cela. +Bien que la chaîne de caractères, soit également rien d'autre qu'un pointeur vers des données sur le tas, +ainsi qu'une variable contenant la capacité de la mémoire sur laquelle pointe le pointeur et son remplissage. + +Le problème principal vient de l'encodage `UTF-8`: c'est un encodage de taille variable qui englobe les caractères +ASCII (qui sont stockés sur un octet) et une très grande quantité d'autres caractères (l'alphabet grec, des emojis, etc.) +qui sont stockés sur 1 à 4 octets. Ainsi chaque lettre de la chaîne de caractère correspond à un nombre variable d'octets +(ce qui n'est pas le cas pour un `Vec`). Il est donc très vivement **déconseillé** de tenter d'indexer +une `String` (faire `s[i]`) comme on le ferait avec un `Vec`. Il faut plutôt utiliser la méthode `.get(i)` qui +interprète les caractères en fonction de la longueur de leurs encodages. + +Une illustration de l'utilisation d'une chaîne de caractère se trouve à la fonction +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:read_command_line_str}} +``` +Cette fonction construit une chaîne de caractères constituées de nombres et d'espaces, +puis la transforme en `Vec<i32>` dont on calculera ensuite le minimum. + +Une `String` est souvent construite à partir d'une "chaîne littérale" à l'aide du trait de conversion `From` +(on les reconnaît parce qu'elles sont entourées de guillemets, `""`) +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:from}} +``` +dont le type est `str` formellement (c'est une chaîne de caractères qui vit durant toute la durée de vie du programme). +Néanmoins, le type `str` n'est jamais utilisé en tant que tel en Rust, mais on utilise plutôt son "slice" +`&str` (plus sur les "tranches" dans la [section suivante](#les-slices)). + +Nous voyons que dans le code ci-dessus nous avons déclaré la variable `s` comme étant mutable, car ensuite nous ajoutons +une slice de chaîne de caractère (de type `&str`) à l'aide de la fonction `push_str()` +dans `s` +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:push_str}} +``` +On peut également ajouter des `char` (ils sont entourés d'apostrophes `' '`) à l'aide de la fonction `push()` +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:push_char}} +``` +Ensuite cette chaîne de caractères est convertie en `Vec<&str>` où chaque élément du `Vec` est un mot, qui est une sous chaîne de `s`. +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:split}} +``` +Finalement, dans +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:string_for}} +``` +on crée un nouveau `Vec<i32>` dans lequel on ajoute les mots convertis en entiers. +On commence par itérer sur le `Vec<&str>` en utilisant les indices de `0` à `s.len()-1` (la longueur du `Vec` `s`). +Puis nous passons à la conversion à proprement parler, bien qu'on fasse des choses un peu compliquées +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:conversion}} +``` +Ici, on commence par récupérer le `i`-ème index de `s` à l'aide de la méthode `get(i)` qui retourne +une `Option<&str>` (si `i` est un indice valide nous avons `Some(s[i])`, sinon `None`). Puis, nous transformons +l'option avec `ok_or()` (nous encapsulons `s[i]` dans un `Ok()` si nous avons un `Some()` et transformons +`None`en `Err("Unable to index")`). Ensuite nous "parsons" `s[i]` et retournons une erreur si le parsing échoue. +Si tout s'est bien passé nous faisons donc un `push()` de chaque `i32` et finissons par retourner le `Vec<i32>` +encapsulé dans un `Ok()`. + +### Les slices + +Un slice est une "tranche" de tableau, statique ou dynamique: une référence vers un bout de mémoire et +la longueur de cette mémoire. + +Ainsi, si nous créons un tableau statique, nous pouvons référencer une "tranche" ou slice +avec la syntaxe suivante +```rust +let a = [1, 2, 3, 4, 5, 6, 7, 8]; +let b = &a[1..4]; // on pointe vers [2, 3, 4] +``` +`b` sera donc une référence et saura que la mémoire sur laquelle elle pointe est de longueur `3` +(cette information permet d'éviter les dépassements de capacité). +On notera la syntaxe `x..y` où `y` est non inclus (comme pour la boucle `for` avec les indices). +Il existe également une syntaxe sans bornes à gauche, à droite, ou à gauche et à droite. +```rust +let a = [1, 2, 3, 4, 5, 6, 7, 8]; +let b = &a[1..]; // on pointe vers [2, 3, .., 8] +let b = &a[..5]; // on pointe vers [1, 2, .., 5] +let b = &a[..]; // on pointe vers [1, 2, .., 8] +``` +Cette syntaxe s'applique également pour toute collection qu'on peut indexer +Le type d'un slice est noté par `&[T]`, où `T` est un type. On en voit un exemple +lorsqu'on veut afficher un tableau par exemple +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/io.rs:print_tab}} +``` +ou encore dans la fonction `find_min()` +```rust,ignore +{{#include ../../codes/rust_lang/collections/src/something_or_nothing.rs:find_min}} +``` +Comme on le voit dans le `main()` l'implémentation à l'aide d'un slice dans les fonction +permet une bien plus grande généricité que si on impose un type `Vec`, un tableau statique, +ou un slice. +```rust,ignore +// Vec +{{#include ../../codes/rust_lang/collections/src/main.rs:vec}} +// Slice +{{#include ../../codes/rust_lang/collections/src/main.rs:ref}} +// Array +{{#include ../../codes/rust_lang/collections/src/main.rs:tab}} +``` + +La notation `&str` représente ainsi une référence vers un `str` qui est une chaîne de caractères +littérale, allouée pour la durée entière d'un programme dans une zone dédiée de la mémoire. +Le type `str` étant "immovable" il n'est jamais utilisé tel quel, mais uniquement via des références. + +Comme pour le slice utilisé pour généraliser le passage en argument des tableaux, +le slice de string `&str` est également utilisé pour généraliser le passage de argument +de chaînes de caractères. + +## Rustlings + +Les rustlings à faire dans ce chapitre sont les suivants: + +### Les `Vec` + +```bash +$ rustlings run vecs1 +$ rustlings run vecs2 +``` + +### Les `String` + +```bash +$ rustlings run strings1 +$ rustlings run strings2 +$ rustlings run strings3 +$ rustlings run strings4 +``` diff --git a/book/src/lifetimes.md b/book/src/lifetimes.md new file mode 100644 index 0000000000000000000000000000000000000000..3c54ee6126ad3207f703378d7f7172fb5edb3237 --- /dev/null +++ b/book/src/lifetimes.md @@ -0,0 +1,261 @@ +# Lifetimes + +## Concepts + +Les concepts abordés dans cet exemple sont: + +1. [Le pattern `NewType`](#le-pattern-newtype) +2. [Les lifetimes](#les-lifetimes) +3. [Les itérateurs et la généricité]() + +Pour plus d'informations sur le pattern `NewType`, vous pouvez vous référer aux +chapitres [19.2](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) et [19.3](https://doc.rust-lang.org/book/ch19-04-advanced-types.html) du livre. +Pour les lifetimes, il y a le [chapitre du livre](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) correspondant, +ainsi que celui du [Rustonomicon](https://doc.rust-lang.org/nomicon/lifetimes.html). + +## Discussion + +Dans ce chapitre, nous allons voir principalement deux concepts différents qui sont importants en Rust +et qu'on retrouve dans beaucoup de code. Les lifetimes, en particulier, sont un sujet complexe +et on verra deux applications différentes mais cela constitue la pointe de l'iceberg des applications +possibles. Le pattern `NewType` est lui bien plus simple, et ne nécessite pas une très longue discussion. + +### Le pattern `NewType` + +Le pattern `NewType` très commun en Rust consiste à encapsuler un type externe à une crate, dans un type local +comme dans +```rust +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype}} +``` +où on encapsule le type externe `Option<T>` dans `SomethingOrNothing<T>`. +Ce type a un paramètre générique `T` et dérive le trait `Debug` qui permet +de faire un affichage détaillé (mais pas très joli) du contenu du type. +Ce pattern est nécessaire pour [implémenter un trait externe sur un type extern](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) (ce qui est interdit en Rust). Ainsi, il ne nous aurait pas été possible d'implémenter +le trait `Display` (qui permet d'afficher une instance du type), `Default` (qui +permet de créer une instance par défaut), ou `PartialEq` (qui permet de vérifier +l'égalité de deux instance du type) directement pour le type `Option<T>`, car +`Display`, `Default`, et `PartialEq` sont des traits externes tout comme le type +`Option<T>`. Pour des types externes l'implémentation de traits externes +est interdite (c'est la *orphan rule*). Cela interdit de "casser" un code externe +en autorisant de multiples implémentations du même trait pour le même type. +Ainsi `SomethingOrNothing<T>` nous permet d'implémenter ces trois traits +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_display}} +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_default}} +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_partialeq}} +``` +Nous aimerions attirer votre attention sur une particularité du *pattern matching* +ici. On voit que nous pouvons faire un match sur des types *imbriqués* +comme pour `SomethingOrNothing(Some(val))`, on va déstructurer les types énumérés +jusqu'à obtenir `val` qui est la valeur qui nous intéresse. + +Une deuxième utilité du pattern NewType est qu'elle permet de limiter +les fonctionnalités d'un type car cela nécessite de réimplémenter +les méthodes qui lui sont propres. Dans notre cas, seule la méthode `unwrap()` +de `Option<T>` nous intéresse (cette fonction retourne la valeur encapsulée dans +la variante `Some()` ou panique si on a un `None`). Ainsi, on n'implémente +que celle-là +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_unwrap}} +``` +On peut noter qu'on a à faire à une `struct` avec des membres anonymes. +Ainsi, les membres peuvent être accédés comme pour les tuples et comme il n'y +en a qu'un dans un `SomethingOrNothing<T>`, on y accède avec le sélecteur `self.0`. + +### Les lifetimes + +Dans cette section nous discutons l'utilisation de l'annotation des lifetimes +dans différents cas: les structures, les méthodes, les traits, et les fonctions. + +#### Les structures + +Dans notre programme, nous utiliserons le type `CustomInt` qui est une représentation d'un entier +qui peut avoir une taille arbitraire (dans les limites de la mémoire de la machine). +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:custom_int}} +``` +Un tel entier est représenté par un signe `i8` (qui peut valoir `+1` ou `-1`), et un +`Vec<u8>`, un tableau dynamique de `u8` (les valeurs admissibles vont de `0` à `9`), +qui contient les chiffres du nombre stockés de droite à gauche: `[7,3,3,1]` +est le nombre `1337`. + +Ces nombres pouvant être gigantesques, nous voulons éviter de les dupliquer lorsque nous +les copions ou les manipulons. Une solution est de stocker uniquement +une référence vers les données, c'est-à-dire que `data` est de type `&Vec<u8>`. +On voit dans le code ci-dessus, qu'il est nécessaire d'annoter la durée de vie +de la référence avec `'a`. Si on omet l'annotation de durée de vie le +compilateur nous préviendra et nous oblige à en spécifier un +```console +error[E0106]: missing lifetime specifier + --> src/custom_int.rs:13:11 + | +13 | data: &Vec<u8>, + | ^ expected named lifetime parameter + | +help: consider introducing a named lifetime parameter + | +9 ~ pub struct CustomInt<'a> { +10 | /// The data contains the unsigned integers that are read from right to left +11 | /// The number 1337 is stored as vec![7, 3, 3, 1]. Each number must be in the range [0,9] +12 | /// and no trailing 0s are allowed. +13 ~ data: &'a Vec<u8>, +``` +Il est en effet impossible pour le compilateur de savoir si la référence vivra +assez longtemps pour vivre plus longtemps qu'une instance de `CustomInt`. + +#### Les méthodes + +L'implémentation des méthodes requiert une annotation sous peine d'erreurs +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:custom_int_impl}} +``` +Il en va de même avec la fonction associée, `try_new()` +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:try_new}} +``` +qui nécessite +une annotation dans la définition du type de `data`. En effet, l'annotation +permet de dire au compilateur que `data`, l'argument de `try_new()`, vit suffisamment +longtemps pour permettre la création d'une instance de `CustomInt`. + +#### Les traits + +Il y a plusieurs traits qui sont implémentés pour `CustomInt<'a>`.: `PartialEq`, `Display`, et `Minumum`. Pour `PartialEq` et `Display`, il suffit de renseigner +l'annotation `'a` à l'instruction `impl` comme pour un paramètre générique, voir +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:partialeq}} +{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:display}} +``` +Il est possible d'écrire la même chose, en omettant l'annotation `'a` +et en la remplaçant par `'_` pour simplifier la notation +```rust,ignore +impl PartialEq for CustomInt<'_> +``` +Comme l'annotation n'est utilisée nulle par, Rust offre ce sucre syntaxique +pour éviter d'écrire trop d'annotations. + +Pour le trait `Minimum` les choses se compliquent un peu. +Pour éviter le copies/clones, on a fait le choix de +n'utiliser que des références dans les arguments, comme dans le type de retour +du calcul du minimum de deux valeurs +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/minimum.rs:minimum}} +``` +Ainsi, comme il y deux références en argument et une référence en sortie, +il est nécessaire d'annoter les références, car sinon le compilateur ne sait pas +avec que durée de vie annoter la sortie (les 2 sont possibles). Ainsi nous annotons +le trait avec la durée de vie `'a`, puis cette durée de vie est utilisée pour toutes +les références dans la fonction `min()`. Ainsi toutes les références ont la même +durée de vie que celle annotée dans le trait. + +Il y a trois implémentation de ce trait: la première est pour les `i32`, la seconde pour `SomthingOrNothing<T>`, et finalement pour `CustomInt`. + +1. Pour l'implémentation +pour les `i32` +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/minimum.rs:min}} +``` +l'implémentation est triviale, il y a uniquement besoin de reprendre +l'annotation pour l'implémentation dans les références en argument +de la fonction et dans le retour de la fonction. En effet, comme les +deux arguments peuvent être retournés, il est nécessaire de préciser +au compilateur que la durée de vie sera la même, sinon il met automatiquement +une durée de vie à chaque argument et reprend celle à `&self` comme la durée +de vie de la sortie. En d'autres termes, sans annotations, on aurait +```rust,ignore +fn min(&self, rhs: &Self) -> &Self +``` +qui serait converti automatiquement par le compilateur en +```rust,ignore +fn min(&'a self, rhs: &'b Self) -> &'a Self { + if self < rhs { + self + } else { + rhs + } +} +``` +et la durée de vie `'a` du retour est pas compatible avec la durée de vie +qui serait retournée au moment de retourner `rhs` (qui est `'b`). Ce qui entraînerait une erreur de compilation (on aime pas ça les erreurs nous). +2. Pour l'implémentation pour `SomethingOrNothing<T>`, nous avons un paramètre générique. +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:min}} +``` +Ainsi nous constatons, que la ligne correspondant à la déclaration +de l'implémentation du trait `Minimum` nécessite la déclaration de la durée de vie `'a`, ainsi que du type générique `T`. On voit dans l'implémentation +de la fonction `min()`, que nous faisons appel à `min()` sur le type `T`, +et que donc celui-ci doit implémenter le trait `Minimum` (tout comme le trait `PartialEq`). +On doit ainsi répercuter la durée de vie sur tous les `Minimum` présents sur la ligne `impl` +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:impl_min}} +``` +Nous ne discutons pas l'implémentation à proprement parler qui est assez raisonnable +pour trouver le minimum de deux valeur encapsulées dans un `NewType`. +3. Finalement, on a l'implémentation pour `CustomInt` qui n'a rien de vraiment nouveau +par rapport aux implémentation précédentes (on réutilise l'annotation `'a` dans `min()` directement), à part la complexité monumentale de la fonction (elle fait plein de lignes) +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:min}} +``` +En effet, on doit faire attention au signe, à la longueur de notre `CustomInt` et +à plusieurs autres joyeusetés. Ici, on peut utiliser le trait `Ord` +(la fonction `cmp()`) pour faire les comparaisons entre le signe et les digits de +nos nombres. Le trait `Ord` représente les opérateurs `<, >, =, <=, >=`, via la fonction `cmp()` qui retourne trois types correspondants +```rust,ignore +Ordering::Less +Ordering::Greater +Ordering::Equal +``` +L'utilisation +d'un type énuméré pour gérer chacun des cas peut sembler verbeux et complexe. Cependant, +il permet de garantir à la *compilation* qu'on a pas oublié de traiter un cas par accident. Et ça, ça n'a pas de prix. + +#### Les fonctions + +Finalement, on utilise les lifetimes dans une fonction qui permet +le calcul du minimum dans un tableau et retourne un `SomethingOrNothing<&T>` contenant une référence vers l'élément le plus petit. + +Cette fonction est générique avec le paramètre `T` et prend en argument une référence qui doivent être annotée. + +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:find_min}} +``` +La fonction `find_min()` prend en argument un *slice* de type générique `T` qui doit implémenter `Minimum`. Comme le type de retour est `SomethingOrNothing<&T>`, donc on +encapsule une référence vers la valeur minimale, il est nécessaire d'annoter +les durées de vies car sinon elles auraient deux valeur différentes ce qui poserait problème au compilateur (car elles doivent être les mêmes). + +L'implémentation de cette fonction, n'est pas très complexe, mais est très intéressante. En premier lieu, pour des question de généricité de l'implémentation +nous passons en argument un *slice* de `T`: cette façon de procéder permet +d'avoir un argument qui serait une référence vers un tableau statique ou vers un `Vec<T>` sans changer l'implémentation. De plus, nous utilisons ici un itérateur +sur le tableau est faisons un `fold()` sur cet itérateur. Le `fold()` prend en argument +un élément neutre (quel est la valeur initiale stockée dans le `fold()`). Ici c'est +```rust,ignore +SomethingOrNothing::default() +``` +puis une fonction anonyme prenant deux arguments +```rust,ignore +|res, x| {} +``` +où la valeur retournée par cette fonction écrase la valeur de `res` à chaque `next()` +de l'itérateur et qui doit avoir le même type que l'élément neutre, et où `x` est la valeur courante de l'itérateur. Ici, le type de `res` est `SomethingOrNothing<&T>` et +le type de `x` est `&T`. La fonction anonyme +```rust,ignore +let r = match res { + SomethingOrNothing(None) => x, + SomethingOrNothing(Some(r)) => r.min(x), +}; +SomethingOrNothing::new(r) +``` +calcule le minimum entre la valeur actuelle stockée dans `res` et `x` en utilisant +la fonction `min()` ce qui implique que `T` doit implémenter `Minimum`. + +### En pratique + +Dans la fonction `main()` de notre programme +```rust,ignore +{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:find_min}} +``` +on crée un tableau de `CustomInt` qui sont créés à partir de références +sur les tableau `v1`, `v2`, etc. qui vivrons ainsi jusqu'à la fin de notre +programme et qui seront promenées sans qu'on ait besoin de les copier à aucun moment. +Les liens entre les durées de vie des références que nous nous sommes efforcés d'annoter dan tout au long ce code sont vérifiées par le compilateur qui +vérifie qu'elles sont toutes valides à la compilation. \ No newline at end of file diff --git a/book/src/part10.md b/book/src/part10.md index 1acace90d165643b41b496ef1c9c50e80ba5f0af..e4839da58b3d91a5d308b4d082b9510b978b35fd 100644 --- a/book/src/part10.md +++ b/book/src/part10.md @@ -1 +1,238 @@ -# Pointeurs intelligents et mémoire \ No newline at end of file +# Discussion du code `part10` + +## Concepts + +Les concepts abordés dans cet exemple sont: + +- [Discussion du code `part10`](#discussion-du-code-part10) + - [Concepts](#concepts) + - [Documentation](#documentation) + - [Discussion](#discussion) + - [Le trait itérateur](#le-trait-itérateur) + - [Les fonctions `iter` et `collect`](#les-fonctions-iter-et-collect) + - [Quelques fonctions d'ordre supérieur sur les itérateurs](#quelques-fonctions-dordre-supérieur-sur-les-itérateurs) + - [Performances et lazy evaluation](#performances-et-lazy-evaluation) + - [Rustlings](#rustlings) + - [Les itérateurs](#les-itérateurs) + +## Documentation + +Afin de compléter ce cours, je vous recommande la lecture des ressources suivantes : + +- [Traitements des collections avec de itérateurs en Rust](https://doc.rust-lang.org/book/ch13-02-iterators.html) +- [Le trait Iterator en Rust](https://doc.rust-lang.org/std/iter/trait.Iterator.html) +- [Les méthodes du trait Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods) + +## Discussion + +### Le trait itérateur + +L'itérateur est un patron de conception extrêment répandu en prorgrammation orientée objet. On le retrouve +dans plusieurs langages comme par exemple python. + +Le trait `Iterator` est défini ainsi en version simplifiée dans la documentation : + +```rust +trait Iterator { + type Item; + fn next(&mut self) -> Option<Self::Item>; +} +``` + +Un itérateur possède donc un élément courant et une méthode `next()` qui permet de passer à l'élément suivant, +puis de retourner l'élément courant. Un itérateur peut-être fini ou infini. Il peut permettre par exemple de +parcourir une collection ou de créer un compteur. + +La documentation Rust offre [un exemple d'implémentation d'itérateur assez accessible](https://doc.rust-lang.org/std/iter/index.html#implementing-iterator). + +### Les fonctions `iter` et `collect` + +Le Rust propose des méthodes permettant de passer d'une collection à un itérateur assez facilement. + +```rust +# fn main(){ + let v : Vec<i32> = vec![1,2,3,4]; + let mut v_iter = v.iter(); + + println!("{}", v_iter.next().unwrap()); + println!("{}", v_iter.next().unwrap()); + println!("{}", v_iter.next().unwrap()); + println!("{}", v_iter.next().unwrap()); + assert!(v_iter.next().is_none()); +# } +``` + +La méthode `iter()` des vecteurs permet de créer un itérateur à partir d'un vecteur. Il faut noter que `v_iter` est mutable, car son état interne est **modifié** par l'appel à la méthode `next()`. +En effet, pour pouvoir avancer dans notre itérateur, nous utilisons la fonction `next()` qui avance l'élément courant d'une position +et retourne une `Option` sur l'élément courant. Pour rappel, la signature de la méthode de `next()` est `fn next(&mut self) -> Option<Self::Item>;`. + +Il est aussi possible de transformer un itérateur en collection à l'aide de la méthode `collect()`. La fonction `from_fn()` permet +de créer un itérateur à l'aide d'une fonction : + +```rust +# fn main(){ + let mut count = 0; + let even_counter = std::iter::from_fn(|| { + count += 2; + Some(count) + }); + + let v : Vec<i32> = even_counter.take(3).collect(); + println!("Les 3 premiers nombres paires sont {}, {}, {}", v[0], v[1], v[2]) +# } +``` + +Le code ci-dessus commence par créer un itérateur infini de nombres paires. Nous verrons plus-tard que les éléments de l'itérateur infini n'est pas généré immédiatement +[dans la section lazy-evaluation](#performances-et-lazy-evaluation). +Nous utilisons une variable mutable `count`, afin de générer les valeurs de notre itérateur. +Notre closure va capturer cette variable et l'incrémenter de 2 à chaque appel et retourner la valeur courante encapsulée dans une `Option`. + +Notre itérateur étant infini, nous devons le limiter à un nombre fini d'éléments avant de pouvoir récupérer une collection. +Pour cela nous utilisons la méthode `take()` qui permet de limiter la taille de notre itérateur. Finalement, nous pouvons +récupérer un vecteur d'entier à l'aide de la méthode `collect()`. + +### Quelques fonctions d'ordre supérieur sur les itérateurs + +L'intérêt principal des itérateurs en Rust réside dans ses fonctions d'ordre supérieur. Elle permettent de traiter des collections +de manière efficaces en apportant des garanties notament en terme de concurrence. On retrouve notament le crate +[Rayon-rs](https://github.com/rayon-rs/rayon) qui permet d'effectuer des traitement en parallèle sans apporter de modifications +majeures au code. + +Ici, nous nous concentrerons sur les méthodes suivantes qui sont très communes dans les langages fonctionnels (pas toujours avec les mêmes noms) : + +- `map()` +- `filter()` +- `fold()` +- `zip()` + +Reprenons notre code de recherche du minimum d'une liste : + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_minimum}} +``` + +Notre fonction prend en argument un vecteur `v` que nous transformons en itérateur avec la méthode `iter()`. Pour trouver le minimum +de notre itérateur, nous utilisons la méthode `fold()`. Cette méthode permet de réduire un itérateur en appliquant itérativement, +une fonction qui prend deux arguments et qui retourne un seul élément. On se retrouve donc avec une +seule valeur à la fin de la réduction. + +La méthode `fold()` prend donc deux arguments. Le premier est la valeur d'initialisation de notre réduction. On parle parfois d'élément neutre, +mais ce terme est plus restrictif qu'une simple valeur initiale. + +L'élément neutre d'une opération est l'élément `N` qui si on lui applique l'opération avec n'importe quel autre élément `x` retourne +cet élément `x`. Si nous prenons l'exemple de l'addition, l'élément neutre est 0, car : +$$ 0 + x = x + 0 = x $$ +et ce peu importe la valeur de x. Il est préférable d'utiliser un élément neutre, plutôt qu'une valeur quelconque pour l'initialisation +de notre réduction. Sans rentrer dans les détails, qui dépassent le cadre de ce cours, les algoritmes parallèles de réduction reposent +sur l'usage d'un élément neutre. Utiliser un élément neutre, vous permettra donc de parallèliser votre code bien plus simplement. + +En l'occurence puisqu'on veut retourner une option, notre élément neutre est `None`. + +Le deuxième argument de notre fonction `fold()` est l'opération de réduction que nous utiliserons pour trouver le minimum. Pour cela nous +utilisons une closure. Cette dernière prend deux argument, un accumulateur (la valeur courante de la réduction) de type `Option<i32>` +et un l'élément courant de l'itérateur qui est de type `&i32`. Notre closure va simplement retourner une Option contenant l'élément le +plus petit entre la valeur actuelle de l'accumulateur et la valeur courante. Cette valeur devient la nouvelle valeur de l'accumulateur +pour l'appel suivant. + +Le résultat de la méthode `fold()` est la dernière valeur de l'accumulateur. Si l'itérateur est vide, la méthode `fold()` retournera la valeur +d'initialisation. Dans notre cas, il s'agit d'une `Option` vide. + +Pour illustrer l'usage de la méthode `filter()`, nous avons une fonction qui trouve le plus petit nombre pair dans un vecteur : + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_even_minimum}} +``` + +La méthode est similaire à la recherche du minimum, mais afin de garder uniquement les nombres pairs on ajoute la fonction `filter()`. Quand on ajoute ainsi +une série de traitements à la suite, on parle de pipelines. + +La fonction `filter()` prend une fonction en paramètre appelée prédicat. Un prédicat et une fonction qui prend un élément et retourne un booléen. +Cette fonction va déterminer quels éléments conserver. Ici nous utilisons une closure qui retourne `true` si le nombre est pair. La méthode `filter()` +va retourner un nouvel itérateur composé uniquement des éléments séléctionnés par le prédicat. + +Pour finir, on recherche la valeur minimum de ce nouvel itérateur, de la même façon que dans l'exemple +précédent, à l'aide de la méthode `fold()`. + +Essayons maintenant de résoudre un problème un peu plus complexe en ajoutant les méthodes `zip()` et `map()`. +Nous aimerions trouver quel est l'élément le plus petit en valeur absolue d'un vecteur donné. + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_absolute_minimum}} +``` + +La première étape consiste à créer deux itérateurs. Le premier contient le signe de chaque élément +et le deuxième, la valeur absolue de chaque élément. + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_absolute_minimum_1}} +``` + +Pour obtenir ces itérateurs, nous allons transformer nos itérateurs obtenus avec `iter()` en utilsant +la fonction `map()`. Cette méthode permet d'appliquer une même transformation sur tous les éléments +d'un itérateur. Pour l'itérateur `signs`, on appelle la méthode `signum()` des `i32`, qui retourne 1 si +le nombre est positif, 0 si le nombre est 0 et -1 si le nombre est négatif. Pour `abs_values`, +nous utilisons la méthode `abs()` des `i32`, qui retourne la valeur absolue d'un nombre. + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_absolute_minimum_2}} +``` + +Maintenant que nous avons nos deux itérateurs, nous aimerions pouvoir itérer sur les deux simultanément. +Pour cela, nous pouvons utiliser la méthode `zip()`. Elle permet de transformer deux itérateurs, en un +unique itérateur de tuple. Ici nous avons deux itérateurs de `i32`, qui deviennent donc un seul itérateur +de type tuple `(i32, i32)`. + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_absolute_minimum_3}} +``` + +Ensuite avec `fold()`, il nous suffit de comparer les valeurs absolues et de retourner une option contenant +le signe et la valeur absolue. + +```rust,ignore +{{#include ../../codes/rust_lang/part10/src/find.rs:find_absolute_minimum_4}} +``` + +Pour finir, on utilise la méthode `map()` de notre `Option<(i32,i32)>` pour multiplier +la valeur absolue par le signe. Ce qui nous donne au final une `Option<i32>` contenant notre résultat. + +### Performances et lazy evaluation + +Les transformations sur les itérateurs sont en général aussi perfomantes qu'une +simple boucle for. On peut voir par exemple [ce benchmark dans le livre Rust](https://doc.rust-lang.org/book/ch13-04-performance.html). +Si on ajoute la possibilité de parallèliser facilement un code basé sur les itérateurs, +leur intérêt paraît évident. + +Un des éléments qui explique les perfomances des itérateurs réside dans la lazy evaluation. +Lorsqu'on appelle une opération de transformation sur un itérateur, la transformation n'est +pas réalisée directement. C'est uniquement lorsqu'on appelle une fonction dite terminale, comme +par exemple `fold()` ou `collect()` qui doivent produire un résulat. Les transformations intermédiaires +peuvent ainsi souvent être fusionnés. + +Ce qui veut dire par exemple que dans le code suivant, + +```rust +# fn main() { + let v = vec![0,1,2,3,4,5]; + let it = v.iter().map(|x| x + 1).map(|x| x * 3).filter(|x| x % 2 == 1); + let res : Vec<i32> = it.collect(); +# } +``` + +que tant que la fonction `collect()` n'a pas été appelée, alors aucune transformation n'est effectuée. +Si vous ne nous croyez pas, un moyen simple de vous en convaincre, consiste à appliquer +une série de transformation sur un itérateur infini et de mesurer la performance. + +## Rustlings + +Les rustlings à faire dans ce chapitre sont les suivants: + +### Les itérateurs + +```bash +$ rustlings run iterators1 +$ rustlings run iterators2 +$ rustlings run iterators3 +$ rustlings run iterators4 +$ rustlings run iterators5 +``` diff --git a/codes/rust_lang/collections/Cargo.toml b/codes/rust_lang/collections/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..50a0d49c74afd47cb41267f168a32f4e80c1b20e --- /dev/null +++ b/codes/rust_lang/collections/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "collections" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/codes/rust_lang/collections/src/io.rs b/codes/rust_lang/collections/src/io.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf0f45476dd09bc12fbbf4a22843e293f86c6ae4 --- /dev/null +++ b/codes/rust_lang/collections/src/io.rs @@ -0,0 +1,68 @@ +//! Contains functions to interact with the user, either +//! by reading inputs from the terminal, either by writing values +//! in it. +use rand::Rng; + +// ANCHOR: read_command_line +/// Poorly emulates the parsing of a command line. +pub fn read_command_line(len: usize) -> Vec<i32> { + let mut rng = rand::thread_rng(); + // ANCHOR: vec_new + let mut v: Vec<i32> = Vec::new(); + // ANCHOR_END: vec_new + // ANCHOR: vec_for + for _i in 0..len { + // ANCHOR: vec_push + v.push(rng.gen()); + // ANCHOR: vec_push + } + // ANCHOR_END: vec_for + v +} +// ANCHOR_END: read_command_line + +// ANCHOR: read_command_line_str +/// Poorly emulates the parsing of a command line. +pub fn read_command_line_str() -> Result<Vec<i32>, String> { + // ANCHOR: from + let mut s = String::from("20 10 48 58 29 0 58 -10 39 5485 394"); + // ANCHOR_END: from + // ANCHOR: push_str + s.push_str(" -100"); + // ANCHOR_END: push_str + // ANCHOR: push_char + s.push(' '); + s.push('1'); + s.push('2'); + // ANCHOR_END: push_char + // ANCHOR: split + let s: Vec<&str> = s.split_ascii_whitespace().collect(); + // ANCHOR_END: split + + // ANCHOR: string_for + let mut v = Vec::new(); + for i in 0..s.len() { + v.push( + // ANCHOR: conversion + s.get(i) + .ok_or(String::from("Unable to index"))? + .parse() + .map_err(|_| format!("Unable to parse {}", s[i]))?, + // ANCHOR_END: conversion + ); + } + // ANCHOR_END: string_for + Ok(v) +} +// ANCHOR_END: read_command_line_str + +/// Prints all the elements of the `tab`. +/// Tab is borrowed here +// ANCHOR: print_tab +pub fn print_tab(tab: &[i32]) { + for t in tab { + print!("{} ", t); + } + println!(); +} +// ANCHOR_END: print_tab diff --git a/codes/rust_lang/collections/src/lib.rs b/codes/rust_lang/collections/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..0b02fce36d04c0f048032fd5600d5df3ea7be30d --- /dev/null +++ b/codes/rust_lang/collections/src/lib.rs @@ -0,0 +1,80 @@ +//! This is an example of Rust crate comments (or inner comments). +//! They will be rendered in the front page of your (crate) library. +//! +//! # How to generate the documentation +//! +//! In this program we wrote an algorithm that computes the minimum of +//! a sequence of integers. +//! +//! To create the documentation run the command +//! ```bash +//! cargo doc +//! ``` +//! The obtain documentation can be found in the `target/doc/collections/index.html` directory +//! +//! To view the documentation type +//! ```bash +//! cargo doc --open +//! ``` +//! which will open the browser and show you the documentation. +//! +//! The documentation supports the CommonMarkdown syntax. +//! +//! Below we will use the `///` comments that will comment the code directly below. +//! We can also sue `//` but they will not be rendered. +//! All the lines written here could be enclosed in `/*! ... */` instead of being prefixed by `//!`. +//! +//! For more informations about writing documentation [follow that link](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html). +//! +//! # Tooling +//! +//! Also Rust comes with great tooling. +//! - [Clippy](https://doc.rust-lang.org/stable/clippy/): The officiel Rust linter. +//! - [Rustfmt](https://github.com/rust-lang/rustfmt): The official Rust code formatter. + +pub mod io; +pub mod minimum; +pub mod something_or_nothing; + +#[test] +fn test_creation() { + use something_or_nothing::SomethingOrNothing; + + let n1: SomethingOrNothing<i32> = SomethingOrNothing::default(); + assert!(n1 == SomethingOrNothing::Nothing); + let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1); + assert!(n2 == SomethingOrNothing::Something(1)); +} + +#[cfg(test)] +mod tests { + use crate::minimum::Minimum; + use crate::something_or_nothing::{find_min, SomethingOrNothing}; + + #[test] + #[should_panic] + fn test_failure_creation() { + let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1); + assert!(n2 == SomethingOrNothing::Nothing); + assert!(n2 == SomethingOrNothing::Something(2)); + } + + #[test] + fn test_min() { + let a = [1, 5, -1, 2, 0, 10, 11, 0, 3]; + let min = find_min(&a); + assert!(min == SomethingOrNothing::Something(-1)); + } + + #[test] + fn test_min_something_or_nothing() { + let x = SomethingOrNothing::Something(5i32); + let y = SomethingOrNothing::Something(10i32); + let z = SomethingOrNothing::Nothing; + assert!(x.min(y) == x); + assert!(y.min(x) == x); + assert!(z.min(y) == y); + assert!(y.min(z) == y); + assert!(z.min(z) == z); + } +} diff --git a/codes/rust_lang/collections/src/main.rs b/codes/rust_lang/collections/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..9f9263a1e5a55d0d5daece7dcdec9159f0d9da5a --- /dev/null +++ b/codes/rust_lang/collections/src/main.rs @@ -0,0 +1,35 @@ +use collections::io; +use collections::something_or_nothing::find_min; + +fn main() -> Result<(), String> { + //ANCHOR: vec + let tab: Vec<i32> = io::read_command_line(10usize); + println!("Among the Somethings in the list:"); + io::print_tab(&tab); + let min = find_min(&tab); + min.print(); + //ANCHOR_END: vec + + let tab = io::read_command_line_str()?; + println!("Among the Somethings in the list:"); + io::print_tab(&tab); + let min = find_min(&tab); + min.print(); + + //ANCHOR: ref + println!("Among the Somethings in the list:"); + io::print_tab(&tab[1..9]); + let min = find_min(&tab[1..9]); + min.print(); + //ANCHOR_END: ref + + //ANCHOR: tab + let tab = [1, 2, 3, 4, 5, 6]; + println!("Among the Somethings in the list:"); + io::print_tab(&tab); + let min = find_min(&tab); + min.print(); + //ANCHOR_END: tab + + Ok(()) +} diff --git a/codes/rust_lang/collections/src/minimum.rs b/codes/rust_lang/collections/src/minimum.rs new file mode 100644 index 0000000000000000000000000000000000000000..a4233159e669e7e910ccf23634b47f599e4d583c --- /dev/null +++ b/codes/rust_lang/collections/src/minimum.rs @@ -0,0 +1,43 @@ +//! Contains a generic trait implementation for computing the minimum between two +//! values. It is the equivalent of the `<` operator. +//! +//! # Examples +//! +//! For integers this would look like +//! +//! ``` +//! # use collections::minimum::Minimum; +//! let one = 1; +//! let two = 2; +//! assert!(Minimum::min(one, two) == one); +//! ``` + +/// The [Minimum] trait computes the minimum value between two values of a type +pub trait Minimum: Copy { + fn min(self, rhs: Self) -> Self; +} + +impl Minimum for i32 { + fn min(self, rhs: Self) -> Self { + if self < rhs { + self + } else { + rhs + } + } +} + +#[cfg(test)] +mod tests { + use crate::minimum::Minimum; + + #[test] + fn test_min_i32() { + let x = 5; + let y = 10; + assert_eq!(Minimum::min(x, y), x); + assert_eq!(Minimum::min(y, x), x); + assert_eq!(Minimum::min(x, x), x); + assert_eq!(Minimum::min(y, y), y); + } +} diff --git a/codes/rust_lang/collections/src/something_or_nothing.rs b/codes/rust_lang/collections/src/something_or_nothing.rs new file mode 100644 index 0000000000000000000000000000000000000000..92fbd9423024d176aa6e9d0f8cc7f11705465135 --- /dev/null +++ b/codes/rust_lang/collections/src/something_or_nothing.rs @@ -0,0 +1,104 @@ +//! Contains the core logic of the library, allowing to tore generic values +//! (or their absence) and manipulate them. + +use crate::minimum::Minimum; + +/// An generic enumerated type that has two variants. +/// +/// - Nothing +/// - Something +#[derive(Clone, Copy)] +pub enum SomethingOrNothing<T> { + /// A [SomethingOrNothing::Nothing] + Nothing, + /// A [SomethingOrNothing::Something] encapsulating a T + Something(T), +} + +impl<T: std::fmt::Display> SomethingOrNothing<T> { + /// A static function that prints the content of a SomethingOrNothing. + pub fn print(&self) { + match self { + SomethingOrNothing::Nothing => println!("Nothing."), + SomethingOrNothing::Something(val) => println!("Something is: {}", val), + } + } +} + +/// Implementation of the [Default] trait that creates a [SomethingOrNothing] +/// that is a `Nothing` variant. +/// +/// # Example +/// +/// ``` +/// # use collections::something_or_nothing::SomethingOrNothing; +/// # fn main() { +/// let def: SomethingOrNothing<i32> = SomethingOrNothing::default(); +/// assert!(def == SomethingOrNothing::Nothing); +/// # } +/// ``` +impl<T> Default for SomethingOrNothing<T> { + /// By Default a [SomethingOrNothing] is a nothing. + fn default() -> Self { + SomethingOrNothing::Nothing + } +} + +/// Implementation of the [PartialEq] trait that is useful for tests. +impl<T: PartialEq> PartialEq for SomethingOrNothing<T> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => true, + (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { + *lhs == *rhs + } + _ => false, + } + } +} + +/// Implementation of the [Minimum] trait used for comparing values +/// in this crate. +impl<T: Minimum> Minimum for SomethingOrNothing<T> { + fn min(self, rhs: Self) -> Self { + match (self, rhs) { + (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { + SomethingOrNothing::Nothing + } + (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { + SomethingOrNothing::Something(lhs.min(rhs)) + } + (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { + SomethingOrNothing::Something(rhs) + } + (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { + SomethingOrNothing::Something(lhs) + } + } + } +} + +/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. +/// Returns a [SomethingOrNothing::Something] containing the the minimum value +/// or [SomethingOrNothing::Nothing] if no minimum value was found. +/// +/// # Example +/// +/// ``` +/// # use collections::something_or_nothing::{SomethingOrNothing, find_min}; +/// # fn main() { +/// let tab = vec![10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_min(&tab); +/// assert!(min == SomethingOrNothing::Something(2)); +/// # } +/// ``` +// ANCHOR: find_min +pub fn find_min<T: Minimum>(tab: &[T]) -> SomethingOrNothing<T> { + let mut minimum = SomethingOrNothing::Nothing; + // Here is T is Copyable. Which means that t is not moved in the loop + for t in tab { + minimum = minimum.min(SomethingOrNothing::Something(*t)); + } + minimum +} +// ANCHOR_END: find_min diff --git a/codes/rust_lang/lifetimes/Cargo.toml b/codes/rust_lang/lifetimes/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bc3f2629be97e53631ee5186294757a3e8844c1d --- /dev/null +++ b/codes/rust_lang/lifetimes/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lifetimes" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/codes/rust_lang/lifetimes/src/custom_int.rs b/codes/rust_lang/lifetimes/src/custom_int.rs new file mode 100644 index 0000000000000000000000000000000000000000..2a4c97e9a4f5214e06d072bc0d755f5d5e3f564d --- /dev/null +++ b/codes/rust_lang/lifetimes/src/custom_int.rs @@ -0,0 +1,198 @@ +use std::cmp::Ordering; + +use crate::minimum::Minimum; + +/// Larger ints based on a [Vec] of [u8] to repensent arbitrary lengthy numbers. +/// The number has a sign as well. +// ANCHOR: custom_int +#[derive(Debug)] +pub struct CustomInt<'a> { + /// The data contains the unsigned integers that are read from right to left + /// The number 1337 is stored as vec![7, 3, 3, 1]. Each number must be in the range [0,9] + /// and no trailing 0s are allowed. + data: &'a Vec<u8>, + /// Contains the sign of the number +/-1; + sign: i8, +} +// ANCHOR_END: custom_int + +// ANCHOR: custom_int_impl +impl<'a> CustomInt<'a> +// ANCHOR_END: custom_int_impl +{ + /// Tries to create a new [CustomInt]. If the number is valid it returns + /// an Ok(CustomInt) an Error otherwise. + /// + /// # Examples + /// + /// ``` + /// use lifetimes::custom_int::CustomInt; + /// let v1 = vec![1, 2, 3, 4]; + /// let num = CustomInt::try_new(&v1, 1); + /// assert!(num.is_ok()); + /// let num = CustomInt::try_new(&v1, -1); + /// assert!(num.is_ok()); + /// let num = CustomInt::try_new(&v1, 10); + /// assert!(num.is_err()); + /// let num = CustomInt::try_new(&v1, -10); + /// assert!(num.is_err()); + /// let v1 = vec![]; + /// let num = CustomInt::try_new(&v1, -1); + /// assert!(num.is_err()); + /// ``` + /// + // ANCHOR: try_new + pub fn try_new(data: &'a Vec<u8>, sign: i8) -> Result<Self, String> { + if data.is_empty() { + Err(String::from("Data is empty.")) + } else if sign == 1 || sign == -1 { + Ok(CustomInt { data, sign }) + } else { + Err(String::from("Invalid sign.")) + } + } + // ANCHOR_END: try_new +} + +// ANCHOR: minimum +impl<'a> Minimum<'a> for CustomInt<'a> +// ANCHOR_END: minimum +{ + // ANCHOR: min + fn min(&'a self, rhs: &'a Self) -> &'a Self { + match self.sign.cmp(&rhs.sign) { + Ordering::Less => return self, + Ordering::Greater => return rhs, + Ordering::Equal => match self.data.len().cmp(&rhs.data.len()) { + Ordering::Less => { + if self.sign == 1 { + return self; + } else { + return rhs; + } + } + Ordering::Greater => { + if self.sign == 1 { + return rhs; + } else { + return self; + } + } + Ordering::Equal => { + for (l, r) in self.data.iter().rev().zip(rhs.data.iter().rev()) { + let ls = (*l as i8) * self.sign; + let rs = (*r as i8) * self.sign; + match ls.cmp(&rs) { + Ordering::Less => return self, + Ordering::Greater => return rhs, + Ordering::Equal => {} + } + } + } + }, + } + self + } + // ANCHOR_END: min +} + +// ANCHOR: partialeq +impl<'a> PartialEq for CustomInt<'a> +// ANCHOR_END: partialeq +{ + fn eq(&self, other: &Self) -> bool { + if self.sign == other.sign && self.data.len() == other.data.len() { + self.data + .iter() + .zip(other.data.iter()) + .try_fold(true, |_, (l, r)| if *l == *r { Some(true) } else { None }) + .is_some() + } else { + false + } + } +} + +// ANCHOR: display +impl<'a> std::fmt::Display for CustomInt<'a> +// ANCHOR_END: display +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // This could be replaced by an `?` + if self.sign == -1 { + write!(f, "-")?; + } + + // This could be replaced by an `?` + let res = self + .data + .iter() + .rev() + .try_fold((), |_, t| write!(f, "{}", t)); + res + } +} + +#[cfg(test)] +mod tests { + use crate::custom_int::CustomInt; + use crate::minimum::Minimum; + use crate::something_or_nothing::find_min; + + #[test] + fn test_creation() { + let v1 = vec![1, 2, 3, 4]; + CustomInt::try_new(&v1, 1).unwrap(); + CustomInt::try_new(&v1, -1).unwrap(); + } + + #[test] + #[should_panic] + fn test_failure_creation_sign() { + let v1 = vec![1, 2, 3, 4]; + CustomInt::try_new(&v1, 10).unwrap(); + } + + #[test] + #[should_panic] + fn test_failure_creation_sign2() { + let v1 = vec![1, 2, 3, 4]; + CustomInt::try_new(&v1, 0).unwrap(); + } + + #[test] + #[should_panic] + fn test_failure_creation_data() { + let v1 = vec![]; + CustomInt::try_new(&v1, 1).unwrap(); + } + + #[test] + fn test_min() { + let mut v = Vec::new(); + let v1 = vec![1, 2, 3, 4]; + let v2 = vec![1, 2, 3]; + let lhs = CustomInt::try_new(&v1, 1).unwrap(); + let rhs = CustomInt::try_new(&v2, 1).unwrap(); + assert!(rhs == *lhs.min(&rhs)); + v.push(lhs); + v.push(rhs); + let lhs = CustomInt::try_new(&v1, -1).unwrap(); + let rhs = CustomInt::try_new(&v2, -1).unwrap(); + assert!(lhs == *lhs.min(&rhs)); + v.push(lhs); + v.push(rhs); + let v1 = vec![1, 2, 3, 4]; + let v2 = vec![1, 2, 5, 4]; + let lhs = CustomInt::try_new(&v1, -1).unwrap(); + let rhs = CustomInt::try_new(&v2, -1).unwrap(); + assert!(rhs == *lhs.min(&rhs)); + v.push(lhs); + v.push(rhs); + let lhs = CustomInt::try_new(&v1, 1).unwrap(); + let rhs = CustomInt::try_new(&v2, 1).unwrap(); + assert!(lhs == *lhs.min(&rhs)); + let min = find_min(&v); + assert_eq!(*min.unwrap(), CustomInt::try_new(&v2, -1).unwrap()); + } +} diff --git a/codes/rust_lang/lifetimes/src/io.rs b/codes/rust_lang/lifetimes/src/io.rs new file mode 100644 index 0000000000000000000000000000000000000000..0f8342e83a34704aa46d882808f59172993f77f6 --- /dev/null +++ b/codes/rust_lang/lifetimes/src/io.rs @@ -0,0 +1,19 @@ +use crate::custom_int::CustomInt; + +/// Prints all the elements of the `tab`. +/// Tab is borrowed here +pub fn print_tab(tab: &Vec<i32>) { + for t in tab { + print!("{} ", t); + } + println!(); +} + +/// Prints all the elements of the `tab`. +/// Tab is borrowed here +pub fn print_tab_custom_int(tab: &Vec<CustomInt>) { + for i in tab { + println!("{i} "); + } + println!(); +} diff --git a/codes/rust_lang/lifetimes/src/lib.rs b/codes/rust_lang/lifetimes/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..239d1374768cf5b5ca2ba754e5ad49e97d720f8d --- /dev/null +++ b/codes/rust_lang/lifetimes/src/lib.rs @@ -0,0 +1,67 @@ +/*! +lifetimes illustrates the use of [Vec] and the Error Handling with [Option] and [Result]. +It also showcases struct enums. +*/ + +pub mod custom_int; +pub mod io; +mod minimum; +pub mod something_or_nothing; + +#[cfg(test)] +mod tests { + use crate::minimum::Minimum; + use crate::something_or_nothing::{find_min, SomethingOrNothing}; + + #[test] + fn test_creation() { + let n1: SomethingOrNothing<i32> = SomethingOrNothing::default(); + assert!(n1 == SomethingOrNothing::default()); + let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); + assert!(n2 == SomethingOrNothing::new(1)); + } + + #[test] + #[should_panic] + fn test_failure_creation() { + let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); + assert!(n2 == SomethingOrNothing::default()); + assert!(n2 == SomethingOrNothing::new(2)); + } + + #[test] + fn test_min() { + let a = vec![1, 5, -1, 2, 0, 10, 11, 0, 3]; + let min = find_min(&a); + assert!(*min.unwrap() == -1); + } + + #[test] + fn test_min_empty() { + let a: Vec<i32> = vec![]; + let min = find_min(&a); + assert!(min == SomethingOrNothing::default()); + } + + #[test] + fn test_min_i32() { + let x = 5; + let y = 10; + assert_eq!(*Minimum::min(&x, &y), x); + assert_eq!(*Minimum::min(&y, &x), x); + assert_eq!(*Minimum::min(&x, &x), x); + assert_eq!(*Minimum::min(&y, &y), y); + } + + #[test] + fn test_min_something_or_nothing() { + let x = SomethingOrNothing::new(5i32); + let y = SomethingOrNothing::new(10i32); + let z = SomethingOrNothing::default(); + assert!(*x.min(&y) == x); + assert!(*y.min(&x) == x); + assert!(*z.min(&y) == y); + assert!(*y.min(&z) == y); + assert!(*z.min(&z) == z); + } +} diff --git a/codes/rust_lang/lifetimes/src/main.rs b/codes/rust_lang/lifetimes/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..4ed86dcc78a35eab943f8dedb97c8f33130d604c --- /dev/null +++ b/codes/rust_lang/lifetimes/src/main.rs @@ -0,0 +1,32 @@ +use lifetimes::custom_int::CustomInt; +use lifetimes::io; +use lifetimes::something_or_nothing::find_min; + +// ANCHOR: main +fn main() -> Result<(), String> { + let v1 = vec![1, 3, 6, 9]; + let v2 = vec![2, 4, 2, 1]; + let v3 = vec![7, 4, 5, 3]; + let v4 = vec![4, 1, 1, 1]; + let v5 = vec![2, 5, 1, 8]; + let v6 = vec![5, 1, 5, 2]; + let v7 = vec![7, 6, 6, 7]; + let v8 = vec![8, 2, 2, 2]; + let lhs = vec![ + CustomInt::try_new(&v1, 1)?, + CustomInt::try_new(&v2, -1)?, + CustomInt::try_new(&v3, 1)?, + CustomInt::try_new(&v4, -1)?, + CustomInt::try_new(&v5, 1)?, + CustomInt::try_new(&v6, 1)?, + CustomInt::try_new(&v7, 1)?, + CustomInt::try_new(&v8, 1)?, + ]; + + println!("Among the custom ints in the list:"); + io::print_tab_custom_int(&lhs); + let min = find_min(&lhs); + println!("The minimum is {min}"); + Ok(()) +} +// ANCHOR_END: main diff --git a/codes/rust_lang/lifetimes/src/minimum.rs b/codes/rust_lang/lifetimes/src/minimum.rs new file mode 100644 index 0000000000000000000000000000000000000000..fdc92933bbb6f8f9bb6acf691ef476441f7badd4 --- /dev/null +++ b/codes/rust_lang/lifetimes/src/minimum.rs @@ -0,0 +1,17 @@ +// ANCHOR: minimum +pub trait Minimum<'a> { + fn min(&'a self, rhs: &'a Self) -> &'a Self; +} +// ANCHOR_END: minimum + +// ANCHOR: min +impl<'a> Minimum<'a> for i32 { + fn min(&'a self, rhs: &'a Self) -> &'a Self { + if self < rhs { + self + } else { + rhs + } + } +} +// ANCHOR_END: min diff --git a/codes/rust_lang/lifetimes/src/something_or_nothing.rs b/codes/rust_lang/lifetimes/src/something_or_nothing.rs new file mode 100644 index 0000000000000000000000000000000000000000..8ce39362c41bbadf69ed2ffe3a01e9515a508919 --- /dev/null +++ b/codes/rust_lang/lifetimes/src/something_or_nothing.rs @@ -0,0 +1,145 @@ +use std::fmt::Display; + +use crate::minimum::Minimum; + +/// An generic enumerated type that encapsulates and Option<T>. +// ANCHOR: newtype +#[derive(Debug)] +pub struct SomethingOrNothing<T>(Option<T>); +// ANCHOR_END: newtype + +impl<T> SomethingOrNothing<T> { + pub fn new(val: T) -> Self { + SomethingOrNothing(Some(val)) + } + + // ANCHOR: newtype_unwrap + pub fn unwrap(self) -> T { + self.0.unwrap() + } + // ANCHOR_END: newtype_unwrap +} + +// ANCHOR: newtype_display +impl<T: Display> std::fmt::Display for SomethingOrNothing<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + SomethingOrNothing(None) => write!(f, "Nothing.")?, + SomethingOrNothing(Some(val)) => write!(f, "Something is: {}", val)?, + } + Ok(()) + } +} +// ANCHOR_END: newtype_display + +// ANCHOR: newtype_default +impl<T> Default for SomethingOrNothing<T> { + /// By Default a [SomethingOrNothing] is a nothing. + fn default() -> Self { + SomethingOrNothing(None) + } +} +// ANCHOR_END: newtype_default + +// ANCHOR: newtype_partialeq +impl<T: PartialEq> PartialEq for SomethingOrNothing<T> { + fn eq(&self, other: &Self) -> bool { + match (&self, &other) { + (SomethingOrNothing(None), SomethingOrNothing(None)) => true, + (SomethingOrNothing(Some(lhs)), SomethingOrNothing(Some(rhs))) => lhs == rhs, + _ => false, + } + } +} +// ANCHOR_END: newtype_partialeq + +// ANCHOR: min +// ANCHOR: impl_min +impl<'a, T: Minimum<'a> + PartialEq> Minimum<'a> for SomethingOrNothing<T> +// ANCHOR_END: impl_min +{ + fn min(&'a self, rhs: &'a Self) -> &'a Self { + match (self, rhs) { + (SomethingOrNothing(None), SomethingOrNothing(None)) => self, + (SomethingOrNothing(Some(l)), SomethingOrNothing(Some(r))) => { + if *l == *l.min(r) { + self + } else { + rhs + } + } + (SomethingOrNothing(None), SomethingOrNothing(Some(_))) => rhs, + (SomethingOrNothing(Some(_)), SomethingOrNothing(None)) => self, + } + } +} +// ANCHOR_END: min + +/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. +/// Returns a [Something] containing the the minimum value +/// or [Nothing] if no minimum value was found. +/// +/// # Examples +/// +/// ``` +/// # use lifetimes::something_or_nothing::{SomethingOrNothing, find_min}; +/// # fn main() { +/// let tab = vec![10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_min(&tab); +/// assert!(*min.unwrap() == 2); +/// # } +/// ``` +/// +/// ``` +/// # use lifetimes::something_or_nothing::{SomethingOrNothing, find_min}; +/// # fn main() { +/// let tab: Vec<i32> = vec![]; +/// let min = find_min(&tab); +/// assert!(min == SomethingOrNothing::default()); +/// # } +/// ``` +// ANCHOR: find_min +pub fn find_min<'a, T: Minimum<'a>>(tab: &'a [T]) -> SomethingOrNothing<&'a T> { + // A very elegant fold applied on an iterator + tab.iter().fold(SomethingOrNothing::default(), |res, x| { + let r = match res { + SomethingOrNothing(None) => x, + SomethingOrNothing(Some(r)) => r.min(x), + }; + SomethingOrNothing::new(r) + }) +} +// ANCHOR_END: find_min + +/// Finds the minimum values contained in two slices and returns the reference +/// towards the slice that contains it. +/// +/// # Examples +/// +/// ``` +/// # use lifetimes::something_or_nothing::{SomethingOrNothing, vec_with_min}; +/// # fn main() { +/// let tab1 = vec![10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let tab2 = vec![10, 32, 12, 43, -2, 53, 83, 2, 9]; +/// +/// let min = vec_with_min(&tab1, &tab2).unwrap(); +/// assert!(min == &tab2); +/// # } +/// ``` +pub fn vec_with_min<'a, T: Minimum<'a> + PartialEq>( + lhs: &'a [T], + rhs: &'a [T], +) -> SomethingOrNothing<&'a [T]> { + match (find_min(lhs), find_min(rhs)) { + (SomethingOrNothing(None), SomethingOrNothing(None)) => SomethingOrNothing::default(), + (SomethingOrNothing(None), SomethingOrNothing(Some(_))) => SomethingOrNothing::new(rhs), + (SomethingOrNothing(Some(_)), SomethingOrNothing(None)) => SomethingOrNothing::new(lhs), + (SomethingOrNothing(Some(l)), SomethingOrNothing(Some(r))) => { + if *l == *l.min(r) { + SomethingOrNothing::new(lhs) + } else { + SomethingOrNothing::new(rhs) + } + } + } +} diff --git a/codes/rust_lang/part08/src/find.rs b/codes/rust_lang/part08/src/find.rs index 0d61d8eca1d8b755ec5b99b6ffc7e631b4e963b3..c8dccdd94b6a885957468b2b62ed635d945259d1 100644 --- a/codes/rust_lang/part08/src/find.rs +++ b/codes/rust_lang/part08/src/find.rs @@ -6,8 +6,8 @@ use crate::binary_operator::BinaryOperator; /// Computes the result of a binary reduction of an Array of a type T. /// Take the binary operation as a function. -/// Returns a [Option::Some] containing the the result value -/// or [Option::None] if the array was empty value was found. +/// Returns a [Option::Some] containing the result value +/// or [Option::None] if the array was empty. /// /// # Example /// diff --git a/codes/rust_lang/part10/Cargo.toml b/codes/rust_lang/part10/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..40bd900f032af074f2f57cab48d0709601a271d6 --- /dev/null +++ b/codes/rust_lang/part10/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "part10" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/codes/rust_lang/part10/src/find.rs b/codes/rust_lang/part10/src/find.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b0e6e3a5a6ebcd5c5df41063eb1d6a9a773d732 --- /dev/null +++ b/codes/rust_lang/part10/src/find.rs @@ -0,0 +1,109 @@ +//! Contains the core logic of the library, allowing to tore generic values +//! (or their absence) and manipulate them. +//! We demonstrates three kind of way to deal with errors + +/// Computes the minimum of a vector of i32. +/// Returns a [Option::Some] containing the minimum value +/// or [Option::None] if the vec was empty. +/// +/// # Example +/// +/// ``` +/// # use part10::find::{find_minimum}; +/// # fn main() { +/// let v = vec![-2, 5, 18, 65, 22, 56, -30]; +/// let min = find_minimum(&v); +/// assert!(min == Some(-30)); +/// # } +/// ``` +// ANCHOR: find_minimum +pub fn find_minimum(v: &Vec<i32>) -> Option<i32> { + v.iter().fold(None, |acc, current| { + let next_acc = if let Some(val) = acc { + if val <= *current { + val + } else { + *current + } + } else { + *current + }; + Some(next_acc) + }) +} +// ANCHOR_END: find_minimum + +/// Computes the smallest even number in a vector of i32. +/// Returns a [Option::Some] containing the smallest even number +/// or [Option::None] if the vec was empty. +/// +/// # Example +/// +/// ``` +/// # use part10::find::{find_even_minimum}; +/// # fn main() { +/// let v = vec![15, 64, 47, 2, 1, 53, 22]; +/// let min = find_even_minimum(&v); +/// assert!(min == Some(2)); +/// # } +/// ``` +// ANCHOR: find_even_minimum +pub fn find_even_minimum(v: &Vec<i32>) -> Option<i32> { + v.iter().filter(|i| *i % 2 == 0).fold(None, |acc, current| { + let next_acc = if let Some(val) = acc { + if val <= *current { + val + } else { + *current + } + } else { + *current + }; + Some(next_acc) + }) +} +// ANCHOR_END: find_even_minimum + +/// Computes the minimum absolute value of a vector of i32. +/// Returns a [Option::Some] containing the minimum abs value +/// or [Option::None] if the vec was empty. +/// +/// # Example +/// +/// ``` +/// # use part10::find::{find_absolute_minimum}; +/// # fn main() { +/// let v = vec![-2, 5, 18, 65, 22, 56, -30]; +/// let min = find_absolute_minimum(&v); +/// assert!(min == Some(-2)); +/// # } +/// ``` +// ANCHOR: find_absolute_minimum +pub fn find_absolute_minimum(v: &Vec<i32>) -> Option<i32> { + // ANCHOR: find_absolute_minimum_1 + let signs = v.iter().map(|i| i.signum()); + let abs_values = v.iter().map(|i| i.abs()); + // ANCHOR_END: find_absolute_minimum_1 + // ANCHOR: find_absolute_minimum_2 + signs + .zip(abs_values) + // ANCHOR_END: find_absolute_minimum_2 + // ANCHOR: find_absolute_minimum_3 + .fold(None, |acc, (c_sign, c_abs_v)| { + let next_acc = if let Some((sign, abs_v)) = acc { + if abs_v <= c_abs_v { + (sign, abs_v) + } else { + (c_sign, c_abs_v) + } + } else { + (c_sign, c_abs_v) + }; + Some(next_acc) + }) + // ANCHOR_END: find_absolute_minimum_3 + // ANCHOR: find_absolute_minimum_4 + .map(|(sign, abs_v)| sign * abs_v) + // ANCHOR_END: find_absolute_minimum_4 +} +// ANCHOR_END: find_absolute_minimum diff --git a/codes/rust_lang/part10/src/io.rs b/codes/rust_lang/part10/src/io.rs new file mode 100644 index 0000000000000000000000000000000000000000..a0e2acf0fc5f99d49229b13275f542c12e4e1692 --- /dev/null +++ b/codes/rust_lang/part10/src/io.rs @@ -0,0 +1,23 @@ +//! Contains functions to interact with the user, either +//! by reading inputs from the terminal, either by writing values +//! in it. + +/// Poorly emulates the parsing of a command line. +pub fn read_command_line_correct() -> Vec<i32> { + vec![-10, 32, 12, -43, 52, -53, 83, -2, 9] +} + +/// Poorly emulates the parsing of a command line. +pub fn read_empty_command_line() -> Vec<i32> { + vec![] +} + +/// Prints all the elements of the vector. +/// vector is borrowed here +pub fn print_vec(v: &Vec<i32>) { + print!("[ "); + for t in v { + print!("{} ", t); + } + println!("]"); +} diff --git a/codes/rust_lang/part10/src/lib.rs b/codes/rust_lang/part10/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..865bce85931b6656d9996d6624a971c796a41a60 --- /dev/null +++ b/codes/rust_lang/part10/src/lib.rs @@ -0,0 +1,28 @@ +//! This crate shows us different ways of dealing with errors in a Rust program. +//! You will find examples of [Option], [Result] and [panic!]. + +pub mod find; +pub mod io; + +#[cfg(test)] +mod tests { + use crate::find::{find_absolute_minimum, find_minimum}; + const VEC: [i32; 9] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; + const VEC_2: [i32; 9] = [-10, 32, 12, -43, 52, -53, 83, -2, 9]; + const MIN_VEC: i32 = 2; + const ABS_MIN_VEC_2: i32 = -2; + + #[test] + fn test_find_minimum() { + let min: Option<i32> = find_minimum(&(VEC.to_vec())); + + assert!(min == Some(MIN_VEC)); + } + + #[test] + fn test_find_absolute_minimum() { + let min: Option<i32> = find_absolute_minimum(&(VEC_2.to_vec())); + + assert!(min == Some(ABS_MIN_VEC_2)); + } +} diff --git a/codes/rust_lang/part10/src/main.rs b/codes/rust_lang/part10/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..33df24f1b266a2e676e2a4f2cedb516132ab5c51 --- /dev/null +++ b/codes/rust_lang/part10/src/main.rs @@ -0,0 +1,26 @@ +use part10::find::{find_absolute_minimum, find_even_minimum, find_minimum}; +use part10::io; + +fn main() { + let v = io::read_command_line_correct(); + println!("Among the elements in the list:"); + io::print_vec(&v); + + let min = find_minimum(&v); + match min { + Some(val) => println!("The minimum value is {}", val), + None => eprintln!("There is no minimum"), + } + + let min = find_absolute_minimum(&v); + match min { + Some(val) => println!("The minimum by absolue value is {}", val), + None => eprintln!("There is no minimum"), + } + + let min = find_even_minimum(&v); + match min { + Some(val) => println!("The smallest even value is {}", val), + None => eprintln!("There is no minimum"), + } +} diff --git a/slides/src/SUMMARY.md b/slides/src/SUMMARY.md index 4c9d844f78e95c27c12bb90dd5991243f695e400..cb42373b38d60a3b066327813fa487299fe532cb 100644 --- a/slides/src/SUMMARY.md +++ b/slides/src/SUMMARY.md @@ -2,6 +2,7 @@ - [Introduction](introduction.md) - [Installation](installation.md). +- [Itérateurs](iterators.md). - [Variables](variables.md). - [Types](types.md). - [Structures de contrôle](control.md). @@ -20,6 +21,6 @@ - [Vecteurs](vec.md). - [Les Strings](string.md). <!-- - [Itérateurs](iterators.md). -- [Unsafe Rust](collections.md). -- [Lifetimes](lifetimes.md). --> +- [Unsafe Rust](collections.md). --> +- [Lifetimes](lifetimes.md). - [Unsafe](unsafe.md). diff --git a/slides/src/errors.md b/slides/src/errors.md index b5d3f70c64d9d40fec18bc6f6cdc38752193d0dc..eb96c9c11a54cad331de84d1d0839e5aa7812041 100644 --- a/slides/src/errors.md +++ b/slides/src/errors.md @@ -74,7 +74,7 @@ fn main() { assert!(denum != 0, "Le dénominateur doit être non nul."); let _total = num / denum; assert_eq!(num, denum, "num et denum devraient être égales."); - assert_ne!(a, b, "num et denum devraient être différentes."); + assert_ne!(num, denum, "num et denum devraient être différentes."); } ``` @@ -121,8 +121,8 @@ fn div(num: i32, denum: i32) -> Option<i32> { fn main() { let num = 1; let denum = 4; - if let res = Some(div(num, denum)) { - println!("{num} / {denum} = res"); + if let Some(res) = div(num, denum) { + println!("{num} / {denum} = {res}"); } else { println!("Une division par zéro est impossible."); } @@ -143,7 +143,7 @@ fn main() { let num = 1; let denum = 0; let res = div(num, denum).unwrap(); // panique! - println!("{num} / {denum} = res"); + println!("{num} / {denum} = {res}"); } ``` diff --git a/slides/src/generics.md b/slides/src/generics.md index e70ac48665a025287ffd57d4233329400236afce..a84135d1316f44b00c01bbe3bf59fd4d237c5488 100644 --- a/slides/src/generics.md +++ b/slides/src/generics.md @@ -62,7 +62,7 @@ struct Point<T> { fn main() { let int_point = Point{ x: 1, y: 2 }; // i32 point let flt_point = Point{ x: 2.3, y: 4.7 }; // f64 point - let does_not_work_point = Point{ x: 1, y: 1.5 } // oups + let does_not_work_point = Point{ x: 1, y: 1.5 }; // oups } ``` diff --git a/slides/src/iterators.md b/slides/src/iterators.md new file mode 100644 index 0000000000000000000000000000000000000000..d7dadb2cc90ed204bfe2f4910b07f30b7cb0a833 --- /dev/null +++ b/slides/src/iterators.md @@ -0,0 +1,143 @@ +# Les itérateurs + +## Le trait itérateur + +Le trait itérateur d'après la documentation + +```rust +trait Iterator { + type Item; + fn next(&mut self) -> Option<Self::Item>; +} +``` + +## Les itérateurs infinis + +```rust[7-11|13-15] +const fn my_random(s: i32) -> i32 { + // PRNG See wikipedia : https://en.wikipedia.org/wiki/Linear_congruential_generator + (((1664525i64 * s as i64) + 1013904223i64) % (1i64 << 32)) as i32 +} + +fn main() { + let mut seed: i32 = 123456; + let mut rand_iter = std::iter::from_fn(move || { + seed = my_random(seed); + Some(seed % 100) + }); + + println! {"{:?}", rand_iter.next()}; + println! {"{:?}", rand_iter.next()}; + println! {"{:?}", rand_iter.next()}; +} +``` + +## La fonction iter + +```rust[3-4|6-9] +fn main() +{ + let v = vec![0, 1, 2]; + let mut v_iter = v.iter(); + + println! {"{:?}", v_iter.next()}; + println! {"{:?}", v_iter.next()}; + println! {"{:?}", v_iter.next()}; + println! {"{:?}", v_iter.next()}; +} +``` + +## La fonction collect + +```rust[7-11|13|14] +const fn my_random(s: i32) -> i32 { + // PRNG See wikipedia : https://en.wikipedia.org/wiki/Linear_congruential_generator + (((1664525i64 * s as i64) + 1013904223i64) % (1i64 << 32)) as i32 +} + +fn main() { + let mut seed: i32 = 123456; + let mut rand_iter = std::iter::from_fn(move || { + seed = my_random(seed); + Some(seed % 100) + }); + + let v_rand: Vec<i32> = rand_iter.take(5).collect(); + println!("{:?}", v_rand); +} +``` +- La fonction `take(5)` crée un nouvel itérateur de 5 éléments. + +## `map()` + +```rust[5|] +fn main() +{ + let v = vec![0, 1, 2]; + let v_res: Vec<i32> = v.iter() + .map(|x| x + 1) + .collect(); + + println! {"{:?}", v_res}; +} +``` + +## `filter()` + +```rust[6|] +fn main() +{ + let v = vec![0, 1, 2]; + let v_res: Vec<i32> = v.iter() + .map(|x| x + 1) + .filter(|x| x % 2 == 1) + .collect(); + + println! {"{:?}", v_res}; +} +``` + +## Rappel élément neutre + +L'élément neutre `N` d'une opération `★` est l'élément tel que `N ★ x = x ★ N = x` pour tout `x`. + +- Exemple : 0 pour l'addition, car `0 + x = x + 0 = x` +- Exemple : 1 pour la multiplication, car `1 * x = x * 1 = x` + +## `fold()` + +```rust[8|4|] +fn main() +{ + let v = vec![0, 1, 2]; + let sum: i32 = v + .iter() + .map(|x| x + 1) + .filter(|x| x % 2 == 1) + .fold(0, |acc, current| acc + current); + + println! {"{:?}", sum}; +} +``` + +## `zip()` + +```rust[6-8|9-11|] +fn main() +{ + let chiffres = vec![1, 2, 3, 4]; + let lettres = vec!['a', 'b', 'c', 'd']; + + chiffres + .iter() + .zip(lettres.iter()) + .for_each( + |(chiffre, lettre)| println!("{lettre} est la lettre n°{chiffre} de l'alphabet",), + ); +} +``` + +## Lazy evaluation et performances + +- La performance des itérateurs est comparable aux boucles +- Les transformations sont appliquées uniquement lorsque que l'on veut récupérer une valeur. On appelle ça **lazy evaluation** diff --git a/slides/src/lifetimes.md b/slides/src/lifetimes.md new file mode 100644 index 0000000000000000000000000000000000000000..b1d7834c84e47ac4b8c935fa5c8a28ec43752480 --- /dev/null +++ b/slides/src/lifetimes.md @@ -0,0 +1,138 @@ +# Lifetimes + +## Ressources + +* [The Book](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) +* [The Rustonomicon](https://doc.rust-lang.org/nomicon/lifetimes.html) + +## Problématique + +```rust [2|3-6|7|] compile_fail +fn main() { + let r; + { + let x = 5; + r = &x; + } + println!("r: {}", r); +} +``` + +## Durée de vie: annotation + +```rust [] compile_fail +fn main() { + let r: &'a i32; // --------+-- 'a + { // | + let x: 'b i32 = 5; // -+--'b | + r = &'b x; // | | + } // -+ | + println!("r: {}", r); // --------+ // x vit plus +} +``` + +* Correction? + +## Inférence + +* Souvent la durée de vie est **inférée**, +* Comme pour les **types**, on doit préciser quand il y a un **doute**, +* Utile que pour la **compilation**: the famous *borrow checker*, +* Si vous devez annoter votre code, il se peut que vous fassiez un truc trop compliqué. + +## Exemple + +```rust [8-13|1,7|] compile_fail +fn longest(x: &str, y: &str) -> &str { + if x.len() > y.len() { + x + } else { + y + } +} // return lifetime is... x or y? +fn main() { + let string1 = String::from("abcd"); + let string2 = "xyz"; + let result = longest(string1.as_str(), string2); + println!("The longest string is {}", result); +} +``` + +## Correction + +```rust [1,7|] +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} // all have the same lifetime +fn main() { + let string1 = String::from("abcd"); + let string2 = "xyz"; + let result = longest(string1.as_str(), string2); + println!("The longest string is {}", result); +} +``` + +## Annotation + +* Indique au compilateur les relations de durées de vie, +* Ne permet pas d'augmenter une durée de vie, +* Le compilateur vérifie que la durée de vie est **compatible** avec la durée de vie. + +```rust [] ignore +&T // référence +&'a T // référence avec durée de vie a +&'a mut T // référence mutable avec durée de vie a +``` + +## Dans une structure + +```rust [1-3|6-8|] +struct VecView<'a> { + view: &'a [i32] +} +fn main() { + let v = vec![1, 2, 3, 4, 5, 6]; + let vv = VecView { + view: &v[1..4], + }; + println!("view = {:?}", vv.view); +} +``` + +* Le compilateur vérifie que `VecView` ne vit pas plus longtemps que sa référence `view`. + +## Élision: fonction + +```rust +fn vecview(v: &[i32], min: usize, max: usize) -> &[i32] { + &v[min..max] +} +``` + +* Règle 1: Chaque référence en argument à sa durée de vie, +* Règle 2: Si unique durée de vie en argument, la sortie obtient la même + +## Élision: méthode + +```rust [1-3|4-9|] +struct VecView<'a> { + view: &'a [i32] +} +impl<'a> VecView<'a> { + fn show(&self, original: &[i32]) -> &[i32] { + println!("we view {:?}, from {:?}", self.view, original); + self.view + } +} +fn main() { + let v = vec![1, 2, 3, 4, 5, 6]; + let vv = VecView { view: &v[1..4] }; + vv.show(&v); +} +``` +* Règle 3: Si un argument est `&self` ou `&mut self`, la sortie a automatiquement la même durée de vie. + diff --git a/slides/src/ownership.md b/slides/src/ownership.md index 97993087f07e278267b9d7fa3af48ccaf8650869..19c914b7776a54b04c93bb0b5c537fc20d8c73b9 100644 --- a/slides/src/ownership.md +++ b/slides/src/ownership.md @@ -24,7 +24,7 @@ - Utilisée pour l'allocation dynamique (la taille des objets peut varier en cours d'exécution). - Peut modifier de la mémoire non-locale à une fonction. - Pas de structure particulière pour l'allocation/désallocation. -- Plus compliqué de gérer l'allocation/déasllocation. +- Plus compliqué de gérer l'allocation/désallocation. - Typiquement plus lent que la pile (il faut chercher de la place pour l'allocation et "sauter" en mémoire pour la retrouver). # La propriété (Ownership) @@ -39,7 +39,7 @@ fn main() { let x = 5; // x est propriétaire de la mémoire contenant 5 { - let y = 6; // y est propriétaire de la mémoire contenant 5 + let y = 6; // y est propriétaire de la mémoire contenant 6 println!("La valeur de (x,y) est: ({}, {}).", x, y); } // y sort de la portée et est détruite avec la valeur 6 println!("La valeur de x est: {}", x); diff --git a/slides/src/traits.md b/slides/src/traits.md index ebbd2436a911b7c2c53185d9f3a21b027a06d0ac..5c508c030fc5474039f1b3ad5c9bc4b8ba7f1d41 100644 --- a/slides/src/traits.md +++ b/slides/src/traits.md @@ -126,7 +126,7 @@ fn main() { - Lors de la définition d'un générique, on peut dire au compilateur si le type implémente un trait. ```rust [1-5|7-9|10-12|] -// T implémente PartialEq (les opérateurs <, >) +// T implémente PartialOrd (les opérateurs <, >) fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } // si on peut pas comparer a et b } // cela ne peut pas compiler, d'où