diff --git a/book/book.toml b/book/book.toml index 9a31d1e76990f25a14ecdbc82df88213983dd9dd..fb0a61ec4b4905ea1919421cfdd947bfee421da1 100644 --- a/book/book.toml +++ b/book/book.toml @@ -4,3 +4,6 @@ language = "fr" multilingual = false src = "src" title = "Rust-101: Université d'été" + +[output.html.playground] +editable = true diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 23058d8797235a29e67c15c12beaa131ab622544..e128166e93532a0dfe5c7c505e3fc73848ebaa77 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -3,3 +3,4 @@ - [Part 00](./part00.md) - [Part 01](./part01.md) - [Part 02](./part02.md) +- [Part 03](./part03.md) diff --git a/book/src/part03.md b/book/src/part03.md new file mode 100644 index 0000000000000000000000000000000000000000..2056a3923eb78ca1daaf1e25539dd69a5a745d3f --- /dev/null +++ b/book/src/part03.md @@ -0,0 +1,275 @@ +# Discussion du code `part03` + +Dans cette partie nous discutons de [ce code](#le-code). + +## Concepts + +Les concepts abordés dans cet exemple sont: + +1. [La généricité et les traits.](#la-généricité-et-les-traits) +2. [Le trait `Minimum`.](#le-trait-minimum) +3. [Les tuples](#les-tuples) +4. [Les traits `Clone` et `Copy`.](#les-traits-clone-et-copy) + +## Discussion + +### La généricité et les traits + +En Rust (comme dans beaucoup de langages) on a un moyen d'éviter de dupliquer du code en +utilisant le concept de *généricité*. Il s'agit ici de remplacer un type par un caractère générique +lors de la définition d'un type ou d'une fonction. + +Jusqu'ici nous avions une structure `NumberOrNothing` qui contenait soit `Nothing` soit `Number(i32)` qui encapsule un entier 32 bits. Afin d'éviter de devoir réécrire tout le code pour chaque +type (`u64`, `double`, ou n'importe quel autre type) on va réécrire notre type énuméré +en le renommant astucieusement `SomethingOrNothing` (en effet, il se pourrait que nous ne voulions +plus uniquement trouver le plus petit nombre dans une liste, mais on pourrait vouloir +trouver le mot le plus "petit" dans l'ordre lexicographique). Ainsi on a + +```rust,no_run +{{#include ../../codes/rust_lang/part03/src/main.rs:something_or_nothing}} +``` + +Il faut noter ici la présence du caractère générique `T`, qui est déclaré comme générique +lors de la définition du type `SomethingOrNothing<T>`, puis est utilisé à la place de `i32` +dans `Something(T)`. + +Maintenant qu'on a changé la définition de noter type, plus rien fonctionne et on doit adapter +le reste du code. Pour aller dans l'ordre, on doit modifier l'implémentation de la fonction `SomethingOrNothing::new(val)` + +```rust,no_run +{{#include ../../codes/rust_lang/part03/src/main.rs:something_or_nothing}} +{{#include ../../codes/rust_lang/part03/src/main.rs:new}} +``` + +On voit ici qu'il faut annoter tout le bloc `impl<T> SomethingOrNothing<T>` avec le type générique +afin qu'il puisse être réutilisé dans les fonctions statiques. En effet, +si on omet les `<T>` on une erreur de compilation +```rust,compile_fail +{{#include ../../codes/rust_lang/part03/src/main.rs:something_or_nothing}} +impl SomethingOrNothing<T> { + fn new(val: T) -> SomethingOrNothing<T> { + SomethingOrNothing::Something(val) + } +} +``` + +Afin d'illustrer une particularité de la généricité de Rust, nous avons également réécrit la fonction +`print(val)` +```rust,no_run +{{#include ../../codes/rust_lang/part03/src/main.rs:something_or_nothing}} +{{#include ../../codes/rust_lang/part03/src/main.rs:print}} +``` +On voit ici qu'il y a une annotation particulière dans l'entête de la fonction, `T: std::fmt::Display` +```rust,ignore +fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) +``` +Cette syntaxe dit au compilateur que le type générique implémente une fonctionnalité +particulière, nommée **trait**, qui dit au programme comment afficher une variable du type +générique `T`. On voit qu'on doit afficher la valeur `val` encapsulée dans `Something(val)` + +```rust,ignore +SomethingOrNothing::Something(val) => println!("Something is: {}", val), +``` + +Hors si on ne dit pas à notre programme comment faire cet affichage, il sera bien embêté. Ici, +nous devons donc préciser qu'il est **nécessaire** que `T` implémente le trait `Display` +sinon le programme ne compilera pas (cliquez sur play pour le vérifier) +```rust,compile_fail +{{#include ../../codes/rust_lang/part03/src/main.rs:something_or_nothing}} +fn print<T>(val: SomethingOrNothing<T>) { + match val { + SomethingOrNothing::Nothing => println!("Nothing."), + SomethingOrNothing::Something(val) => println!("Something is: {}", val), + } +} +``` +On retrouve la contrainte d'implémenter le trait `Display` dans la fonction `print_tab(tab)`. +Corriger le code ci-dessous pour qu'il compile +```rust,compile_fail,editable +const SIZE: usize = 9; +fn read_command_line() -> [i32; SIZE] { + [10, 32, 12, 43, 52, 53, 83, 2, 9] +} +fn print_tab<T>(tab: [T; SIZE]) { + for t in tab { + print!("{} ", t); + } + println!(); +} +fn main() { + print_tab(read_command_line()); +} +``` + +### Le trait `Minimum` + +La fonctionnalité principale dont nous avons besoin pour que notre code fonctionne est de pouvoir trouver +le minimum d'une liste de `SomethingOrNothing<T>` (voir l'appel à la fonction `current_minimum.min(SomethingOrNothing::new(t))`). + +```rust,ignore +{{#include ../../codes/rust_lang/part03/src/main.rs:find_min}} +``` + +Ainsi on doit annoter `T` pour qu'il puisse calculer la plus petite valeur entre deux `SomethingOrNothing<T>`. On va donc devoir écrire définir notre premier *trait*. +On définit un trait à l'aide de la syntaxe suivante +```rust,no_run +{{#include ../../codes/rust_lang/part03/src/main.rs:minimum}} +``` +Ici le trait `Minimum` sera implémenté sur un type qui implémente le trait `Copy` (un trait qui +garantit qu'on sait comment copier une valeur). Notre trait n'a que la fonction `min` (le nombre +de fonction dans un trait est arbitraire, il peut même être nul). +L'entête de la fonction nous dit que la fonction `min` a deux argument, la variable sur laquelle +elle est appelée, et une variable du même type (annoté avec le type `Self`, avec un "s" +majuscule, contrairement à `self` qui fait référence à une variable) et retourne également une +valeur du même type. +Il est important de vous rappeler qu'ici on ne sait pas encore quel est le type sur lequel on implémente cette fonction. + +L'implémentation de `Minimum` pour `SomethingOrNothing<T>` se fait comme suit + +```rust,ignore +{{#include ../../codes/rust_lang/part03/src/main.rs:minimum}} +{{#include ../../codes/rust_lang/part03/src/main.rs:minimum_impl}} +``` + +Pour implémenter un trait sur un type on utilise la syntaxe + +```rust,ignore +impl Trait for Type +``` + +puis implémenter **toutes** les fonctions se trouvant dans la définition du trait. +Ici, nous n'avons que la fonction `min` à implémenter. + +Le type `SomethingOrNothing` est générique, donc il faut à nouveau notre code +pour en tenir compte. On voit que le type générique doit lui-même +implémenter le trait `Minimum` pour que cette fonction compile: on voit l'utilisation +de la fonction `min` + +```rust,ignore +SomethingOrNothing::new(lhs.min(rhs)) +``` + +Nous verrons le détail de la syntaxe de cette fonction dans la section sur [les tuples](#les-tuples). + +Comme nous utilisons des `SomethingOrNothing<i32>` dans ce code, nous devons implémenter +le trait `Minimum` pour des entiers. Ce qui est fait dans le bout de code suivant + +```rust,no_run +{{#include ../../codes/rust_lang/part03/src/main.rs:minimum}} +{{#include ../../codes/rust_lang/part03/src/main.rs:minimum_i32}} +``` + +### Les tuples + +On découvre ici, une syntaxe inconnue: les **tuples**. Un tuple est une collection +d'un nombre arbitraire de valeurs dont les types peuvent être différents. +Un tuple lui-même est une valeur dont le type est + +```rust,ignore +(T1, T2, T3, ...) +``` +où `T1`, `T2`, `T3`, ... sont les types des membres du tuple. Un tuple peut être utilisé pour retourner plusieurs valeurs depuis une fonction par exemple. + +Ainsi quand on fait du *pattern mathching* + +```rust,ignore +match (self, rhs) +``` + +on va créer un tuple avec les valeurs de `self` et de `rhs` et vérifier les types de toutes les valeurs possibles pour ces types énumérées. On a donc 4 cas différents: + +```rust,ignore +match (self, rhs) { + (SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => { + SomethingOrNothing::Nothing + } + (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { + SomethingOrNothing::new(lhs.min(rhs)) + } + (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { + SomethingOrNothing::new(rhs) + } + (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { + SomethingOrNothing::new(lhs) + } +} +``` + +quand on a deux variantes `Nothing`, on retourne `Nothing`, quand on a une variante `Nothing` +et une valeur `Something(val)` on retourne `Something(val)` et finalement quand on a +`Something(lhs)` et un `Something(rhs)`, c'est `Something(lhs.min(rhs))` qui est retourné (donc +la valeur la plus petite qui encapsulée). D'où la nécessité que `T` implémente le trait `Minimum`. +Il faut noter à nouveau qu'il n'y a pas de `;` dans les blocs des variantes du pattern matching et que les valeurs sont retournées (la structure de contrôle `match` est une expression). + +### Les traits `Clone` et `Copy` + +En Rust, il existe deux traits essentiels `Copy` et `Clone`. Le trait `Copy` permet de copier les instances d'un type bit à bit. La copie est une action **implicite**. Par exemple, dans le code ci-dessous + +```rust,ignore +let y : i32 = 5; +let x = y; +``` + +la valeur de `y` (ici `5`) est copiée dans une zone mémoire nouvellement allouée qui ensuite est liée à la variable `x`. +Dans notre code nous décidons d'autoriser la copie de notre type énuméré en implémentant le trait `Copy` + +```rust,no_run +{{#rustdoc_include ../../codes/rust_lang/part03/src/main.rs:copy}} +``` + +Comme on peut le voir ici, il n'y a pas de fonction à implémenter avec `Copy`, ce trait permet uniquement d'effectuer une copie binaire des données. + +Il est également important de noter qu'afin que notre type `SomethingOrNothing<T>` implémente le trait `Copy`, il est nécessaire que le type paramètrique `T` implémente lui aussi `Copy`. Ce qui veut dire plus simplement qu'un type complexe ne peut pas implémenter le trait `Copy` si les types qu'il contient ne sont pas copiable. + +On peut aussi remarquer qu'il est possible d'indiquer la nécessité que les types implémentant un trait soit copiables. Par exemple + +```rust,no_run +{{#rustdoc_include ../../codes/rust_lang/part03/src/main.rs:minimum}} +``` +où la notation `trait Minimum: Copy` spécifie que `Copy` doit être implémenté quand on implémente `Minimum`. +Ici tous les types qui implémentent le trait `Minimum` doivent également implémenter `Copy`. C'est le cas par exemple du type `i32` + +```rust,no_run +{{#rustdoc_include ../../codes/rust_lang/part03/src/main.rs:minimum_i32}} +``` + +Le deuxième trait que nous retrouvons dans ce code est le trait `Clone`. `Clone` est un supertrait de `Copy`, ce qui signifie qu'un type qui implémente `Copy` doit nécessairement implémenter `Clone`. +Le trait `Clone` permet de dupliquer explicitement une instance. En effet, pour cloner une instance, il faut appeler la méthode `clone()` explicitement + +```rust,no_run +{{#rustdoc_include ../../codes/rust_lang/part03/src/main.rs:clone}} +``` + +Comme on peut le voir dans le code ci-dessus, il est possible de définir un comportement arbitraire en redéfinissant la méthode `clone()`. +En effet, le trait `Clone` nous permet de définir librement le comportement attendu lors d'un clonage, ce qui n'est pas le cas avec `Copy`. +Cette liberté a un coût, puisque que l'on peut écrire notre propre fonction de clonnage, ce dernier peut facilement devenir beaucoup plus coûteux que la simple copie binaire des données. + +Le langage Rust offre un attribut afin de pouvoir implémenter simplement les traits `Copy` et `Clone`. Il s'agit de `#[derive(...)]`. +Par exemple avec le code suivant + +```rust,no_run +#[derive(Clone,Copy)] +struct MyStruct; +``` + +l'annotation permet d'indiquer au compilateur que notre `struct MyStruct` nécessite une implémentation par défaut des traits `Copy` et `Clone`. +Ce qui est équivaut à écrire + +```rust,no_run +struct MyStruct; + +impl Copy for MyStruct { } + +impl Clone for MyStruct { + fn clone(&self) -> MyStruct { + *self + } +} + +``` + +## Le code + +```rust +{{#rustdoc_include ../../codes/rust_lang/part03/src/main.rs:main}} +``` diff --git a/codes/run_tests.sh b/codes/run_tests.sh index feb0a29ac894795b6b4616658ab33ee2b1c2abf8..8c2e74c3c4c29afb4dde329ea2d9140ff8ae320a 100755 --- a/codes/run_tests.sh +++ b/codes/run_tests.sh @@ -4,7 +4,12 @@ make -C c_lang/min_list cd rust_lang for d in *; do echo ==================== Running cargo run for $d ==================== - cargo run --manifest-path $d/Cargo.toml + if [ $d == 'part07' ] + then + cargo run --manifest-path $d/Cargo.toml < $d/numbers.txt + else + cargo run --manifest-path $d/Cargo.toml + fi echo ==================== Running cargo test for $d ==================== cargo test --manifest-path $d/Cargo.toml --workspace --verbose echo ==================== Running cargo doc for $d ==================== diff --git a/codes/rust_lang/part03/src/main.rs b/codes/rust_lang/part03/src/main.rs index 7cadbdc27915aa2495b46109fc526c770519620e..0f8ebd93ffd481640068449ac8679d6429ef3e92 100644 --- a/codes/rust_lang/part03/src/main.rs +++ b/codes/rust_lang/part03/src/main.rs @@ -1,39 +1,57 @@ +/* ANCHOR: all */ + /// In part03 we introduce genericity through traits and in particular, [Copy], /// [Clone], [std::fmt::Display] . - +// ANCHOR: something_or_nothing enum SomethingOrNothing<T> { Nothing, Something(T), } +// ANCHOR_END: something_or_nothing + +// ANCHOR: new +impl<T> SomethingOrNothing<T> { + fn new(val: T) -> SomethingOrNothing<T> { + SomethingOrNothing::Something(val) + } +} +// ANCHOR_END: new +// ANCHOR: print +// Print function // We know the generic type T must be Displayable -impl<T: std::fmt::Display> SomethingOrNothing<T> { - // Static function - fn print(self) { - match self { - SomethingOrNothing::Nothing => println!("Nothing."), - SomethingOrNothing::Something(val) => println!("Something is: {}", val), - } +fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) { + match val { + SomethingOrNothing::Nothing => println!("Nothing."), + SomethingOrNothing::Something(val) => println!("Something is: {}", val), } } +// ANCHOR_END: print +// ANCHOR: clone impl<T: Clone> Clone for SomethingOrNothing<T> { fn clone(&self) -> Self { match self { SomethingOrNothing::Nothing => SomethingOrNothing::Nothing, - SomethingOrNothing::Something(val) => SomethingOrNothing::Something(val.clone()), + SomethingOrNothing::Something(val) => SomethingOrNothing::new(val.clone()), } } } +// ANCHOR_END: clone +// ANCHOR: copy impl<T: Copy> Copy for SomethingOrNothing<T> {} +// ANCHOR_END: copy // If we remove Copy, we have a problem with the t in tab // in the computation of the minimum. +// ANCHOR: minimum trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } +// ANCHOR_END: minimum +// ANCHOR: minimum_impl impl<T: Minimum> Minimum for SomethingOrNothing<T> { fn min(self, rhs: Self) -> Self { match (self, rhs) { @@ -41,20 +59,22 @@ impl<T: Minimum> Minimum for SomethingOrNothing<T> { SomethingOrNothing::Nothing } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => { - SomethingOrNothing::Something(lhs.min(rhs)) + SomethingOrNothing::new(lhs.min(rhs)) } (SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => { - SomethingOrNothing::Something(rhs) + SomethingOrNothing::new(rhs) } (SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => { - SomethingOrNothing::Something(lhs) + SomethingOrNothing::new(lhs) } } } } +// ANCHOR_END: minimum_impl // i32 is Copyable as a very basic type as f32, f64, etc. // Arrays for example are not copyable. +// ANCHOR: minimum_i32 impl Minimum for i32 { fn min(self, rhs: Self) -> Self { if self < rhs { @@ -64,6 +84,7 @@ impl Minimum for i32 { } } } +// ANCHOR_END: minimum_i32 const SIZE: usize = 9; @@ -73,31 +94,34 @@ fn read_command_line() -> [i32; SIZE] { // Prints tab and returns tab. // Tab would be destructed at the end of the function otherwise. -fn print_tab(tab: [i32; SIZE]) -> [i32; SIZE] { +// ANCHOR: print_tab +fn print_tab<T: std::fmt::Display>(tab: [T; SIZE]) { for t in tab { print!("{} ", t); } println!(); - tab } +// ANCHOR_END: print_tab -fn find_min<T: Minimum>(tab: [T; SIZE]) -> ([T; SIZE], SomethingOrNothing<T>) { - let mut min = SomethingOrNothing::Nothing; +// ANCHOR: find_min +fn find_min<T: Minimum>(tab: [T; SIZE]) -> SomethingOrNothing<T> { + let mut current_minimum = SomethingOrNothing::Nothing; // Here is T is not Copyable tab is consumed and cannot be returned for t in tab { - min = min.min(SomethingOrNothing::Something(t)); + current_minimum = current_minimum.min(SomethingOrNothing::new(t)); } - (tab, min) + current_minimum } +// ANCHOR_END: find_min +// ANCHOR: main fn main() { let tab = read_command_line(); - println!("Among the Somethings in the list:"); - let tab = print_tab(tab); - - // There are alternatives to access fields of tuples - let (_, min) = find_min(tab); - // The first field is not used therefore we can replace it with "_" - min.print(); + print_tab(tab); + let min = find_min(tab); + print(min); } +// ANCHOR_END: main + +/* ANCHOR_END: all */ diff --git a/codes/rust_lang/part07/numbers.txt b/codes/rust_lang/part07/numbers.txt new file mode 100644 index 0000000000000000000000000000000000000000..96c95dafa6dc00eb4f7fb393cdf44412e655b7a3 --- /dev/null +++ b/codes/rust_lang/part07/numbers.txt @@ -0,0 +1,5 @@ +3478 +26 +-127 +1287 +189 \ No newline at end of file diff --git a/codes/rust_lang/part08/src/io.rs b/codes/rust_lang/part08/src/io.rs index 496c227a0a46ba1cbecd6488b2eede64a61b3e3c..482df9d6e0fa6240028d607a081cb060aed74387 100644 --- a/codes/rust_lang/part08/src/io.rs +++ b/codes/rust_lang/part08/src/io.rs @@ -49,7 +49,7 @@ pub fn print_tab(tab: &Vec<i32>) { /// Prints all the elements of the `tab`. /// Tab is borrowed here -pub fn print_tab_CustomInt(tab: &Vec<CustomInt>) { +pub fn print_tab_custom_int(tab: &Vec<CustomInt>) { for i in tab { println!("{i} "); } diff --git a/codes/rust_lang/part08/src/main.rs b/codes/rust_lang/part08/src/main.rs index 4fd0479b6ab6e773f7552e93276b00b6e1e0623c..1908de7c9f8125639a927f76a5cd61dabf48efe9 100644 --- a/codes/rust_lang/part08/src/main.rs +++ b/codes/rust_lang/part08/src/main.rs @@ -7,7 +7,7 @@ fn main() -> Result<(), String> { Err(s) => return Err(s), }; println!("Among the custom ints in the list:"); - io::print_tab_CustomInt(&tab); + io::print_tab_custom_int(&tab); // There are alternatives to access fields of tuples let min = find_min(&tab); // The first field is not used therefore we can replace it with "_"