From 48154de5df7b9242a22d14633e1f19df17fa0400 Mon Sep 17 00:00:00 2001 From: "orestis.malaspin" <orestis.malaspinas@hesge.ch> Date: Tue, 15 Aug 2023 17:31:19 +0200 Subject: [PATCH] Switches part05 and part06 --- .gitlab-ci.yml | 43 --- README.md | 4 +- book/book.toml | 22 +- book/src/SUMMARY.md | 2 + book/src/part05.md | 201 ++++++++++++ book/src/part06.md | 305 ++++++++++++++++++ codes/rust_lang/part00/src/main.rs | 4 +- codes/rust_lang/part05/src/io.rs | 15 + codes/rust_lang/part05/src/lib.rs | 12 + codes/rust_lang/part05/src/main.rs | 215 +----------- codes/rust_lang/part05/src/minimum.rs | 15 + .../part05/src/something_or_nothing.rs | 53 +++ codes/rust_lang/part06/src/io.rs | 8 + codes/rust_lang/part06/src/lib.rs | 80 +++-- codes/rust_lang/part06/src/main.rs | 2 - codes/rust_lang/part06/src/main_old.rs | 175 ++++++++++ codes/rust_lang/part06/src/minimum.rs | 38 ++- .../part06/src/something_or_nothing.rs | 28 ++ 18 files changed, 932 insertions(+), 290 deletions(-) create mode 100644 book/src/part05.md create mode 100644 book/src/part06.md create mode 100644 codes/rust_lang/part05/src/io.rs create mode 100644 codes/rust_lang/part05/src/lib.rs create mode 100644 codes/rust_lang/part05/src/minimum.rs create mode 100644 codes/rust_lang/part05/src/something_or_nothing.rs create mode 100644 codes/rust_lang/part06/src/main_old.rs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d88b4a..ed6642a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,44 +1,3 @@ -# # Official language image. Look for the different tagged releases at: -# # https://hub.docker.com/r/library/rust/tags/ -# image: "rust:1.70-alpine3.17" -# # Use cargo to test the project -# before_script: -# - apk add --no-cache musl-dev curl -# - mkdir -p $HOME/.cargo/bin -# - curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.31/mdbook-v0.4.31-x86_64-unknown-linux-musl.tar.gz | tar -xz --directory=$HOME/.cargo/bin -# - curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v0.7.7/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip -# - unzip mdbook-linkcheck.zip -d $HOME/.cargo/bin && chmod +x $HOME/.cargo/bin/mdbook-linkcheck -# # - cargo install mdbook-linkcheck -# - rustup component add rustfmt -# - rustup component add clippy -# - export PATH=$PATH:$HOME/.cargo/bin -# ## -# ## Install ssh-agent if not already installed, it is required by Docker. -# ## -# - 'which ssh-agent || (apk add --update openssh-client)' -# - 'which rsync || (apk add --update rsync)' -# - 'which chmod || (apk add --update chmod)' -# ## -# ## Run ssh-agent (inside the build environment) -# ## -# - eval $(ssh-agent -s) -# ## -# ## Give the right permissions, otherwise ssh-add will refuse to add files -# ## Add the SSH key stored in SSH_PRIVATE_KEY file type CI/CD variable to the agent store -# ## -# - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null -# ## -# ## Create the SSH directory and give it the right permissions -# ## -# - mkdir -p ~/.ssh -# - chmod 700 ~/.ssh -# ## -# ## Assuming you created the SSH_KNOWN_HOSTS file type CI/CD variable, uncomment the -# ## following two lines. -# ## -# - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts -# - chmod 644 ~/.ssh/known_hosts - # Official language image. Look for the different tagged releases at: # https://hub.docker.com/r/library/rust/tags/ image: "rust:1-slim-bookworm" @@ -50,7 +9,6 @@ before_script: - curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.31/mdbook-v0.4.31-x86_64-unknown-linux-musl.tar.gz | tar -xz --directory=$HOME/.cargo/bin - curl -L https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/download/v0.7.7/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -o mdbook-linkcheck.zip - unzip mdbook-linkcheck.zip -d $HOME/.cargo/bin && chmod +x $HOME/.cargo/bin/mdbook-linkcheck - # - cargo install mdbook-linkcheck - rustup component add rustfmt - rustup component add clippy - export PATH=$PATH:$HOME/.cargo/bin @@ -92,7 +50,6 @@ test:cargo: - rustc --version && cargo --version # Print version info for debugging - cargo clippy --version - rustfmt --version - - ls -ltr $HOME/.cargo/bin - mdbook --version && mdbook-linkcheck --version build:codes: diff --git a/README.md b/README.md index 908ce70..6876a96 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ Le cours théorique est découpé comme suit: - struct, 3. Généricité et traits (Clone, Copy p.ex.). 4. Ownership, Borrowing. -5. Tests, documentation, outils variés (rustfmt, clippy, etc). -6. Modules et visibilité. +5. Modules et visibilité. +6. Tests, documentation, outils variés (rustfmt, clippy, etc). 7. Gestion d'erreurs (Option, Result) 8. Collections (Vec, HashMap, etc) 9. Itérateurs, fonctions d'ordre supérieur, lambdas. diff --git a/book/book.toml b/book/book.toml index 78ab4a0..8888cd7 100644 --- a/book/book.toml +++ b/book/book.toml @@ -8,15 +8,15 @@ title = "Rust-101: Université d'été" [output.html.playground] editable = true -[output.linkcheck] -# Should we check links on the internet? Enabling this option adds a -# non-negligible performance impact -follow-web-links = true +# [output.linkcheck] +# # Should we check links on the internet? Enabling this option adds a +# # non-negligible performance impact +# follow-web-links = true -# How should warnings be treated? -# -# - "warn" will emit warning messages -# - "error" treats all warnings as errors, failing the linkcheck -# - "ignore" will ignore warnings, suppressing diagnostic messages and allowing -# the linkcheck to continuing -warning-policy = "error" +# # How should warnings be treated? +# # +# # - "warn" will emit warning messages +# # - "error" treats all warnings as errors, failing the linkcheck +# # - "ignore" will ignore warnings, suppressing diagnostic messages and allowing +# # the linkcheck to continuing +# warning-policy = "error" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index e3ee109..f0ad93e 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,3 +7,5 @@ - [Part 02](./part02.md) - [Part 03](./part03.md) - [Part 04](./part04.md) +- [Part 05](./part05.md) +- [Part 06](./part06.md) diff --git a/book/src/part05.md b/book/src/part05.md new file mode 100644 index 0000000..ee24553 --- /dev/null +++ b/book/src/part05.md @@ -0,0 +1,201 @@ +# Discussion du code `part05` + +Dans cette partie nous discutons de [ce code](#le-code). + +## Concepts + +Les concepts abordés dans cet exemple sont: + +1. [Les modules.](#les-modules) +2. [La visibilité.](#la-visibilité) + +## Discussion + +Jusqu'ici, tout le code était contenu dans le fichier `main.rs`. Au fur et à mesure que le code devient plus complexe et long, il est nécessaire de le séparer en plusieurs fichiers (modules) et de gérer +la visibilité des structures, types énumérés, fonctions, etc. Nous allons voir dans ce chapitre comment +cela se passe pour le Rust. Pour plus d'informations vous pouvez vous référer à [La Bible du Rust](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html). + +### Les modules + +Afin de séparer le code en plusieurs fichiers il est nécessaire de créer un fichier `lib.rs` qui se trouve dans le même répertoire que le fichier `main.rs`. Ce répertoire est en général le répertoire `src` de votre projet. Ici c'est dans `projet05/src`. Dans notre cas, il contient très peu de lignes + +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/lib.rs:lib_modules}} +``` + +La présence d'un fichier `lib.rs` indique que vous avez créé une *librairie*, appelée `crate` en Rust. +Toutes les libraries publiées en Rust sont des `crate` et peuvent se télécharger depuis le site +[crates.io](https://crates.io). + +On voit qu'il y a dans le fichier `lib.rs` la définition de la constante `SIZE`, ainsi que trois lignes +contenant le mot-clé `mod` qui indique la présence d'un **module**. Par défaut, le compilateur Rust va aller chercher +le contenu de ces modules dans les fichiers `io.rs`, `minimum.rs`, et `something_or_nothing.rs` +(ou `io/mod.rs`, `minimum/mod.rs`, et `something_or_nothing/mod.rs`). Dans ce chapitre, nous avons simplement réparti tout le code qui se trouvait dans `main.rs` dans la [partie 4](part04.md). Le mot-clé +`pub` indique la **visibilité** du module à l'intérieur de votre librairie. +Ainsi, le module `minimum` n'est pas exposé à vos utilisatrices et utilisateurs, alors que `io` et `something_or_nothing` le sont. Nous verrons un peu plus bas les règles sur la visibilité. + +Afin d'utiliser les fonctions définies dans notre librairie dans notre programme principal (le `main.rs`) +comme dans le code suivant + +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/main.rs:main_imports}} +``` + +Pour importer les modules avec la syntaxe suivante +```rust,ignore +use nom_de_la_crate::nom_du_module; +``` +où le `nom_de_la_crate` est défini dans le fichier `part05/Cargo.toml` (le champs `name`), +le nom du module ici est `io` et chaque module est séparé par le symbole `::`. +On a également importé la fonction `find_min` spécifiquement +avec la syntaxe +```rust,ignore +use nom_de_la_crate::nom_du_module::nom_de_la_fonction; +``` + +Ce n'est pas fait dans cet exemple, mais il est tout à fait possible de définir des sous-modules +(voir [Le Livre](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)). + +Afin d'utiliser de partager des fonctions entre les modules, il faut également les importer comme dans le module `something_or_nothing` qui nécessite l'import du trait `Minimum` à l'aide de la syntaxe + +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/something_or_nothing.rs:minimum}} +``` + +On voit la nécessité d'utiliser le mot-clé `crate` pour indiquer que le module est importé depuis +l'intérieur de notre librairie. Puis il faut suivre l'arborescence habituelle avec le module `minimum` +et le trait `Minimum` le tout séparé par des séparateurs, `::`. + +#### Observations + +1. Observez ce qui se passe si vous commentez la ligne `mod minimum;` dans `lib.rs` et tentez de compiler le code. Que vous dit le compilateur? +2. Que se passe-t-il, si vous enlevez le mot clé `pub` de la ligne `pub mod io;` dans `lib.rs` et tentez de compiler le code? Quel message s'affiche? + +On constate que pour la partie `1`, le compilateur n'est pas content, car il ne trouve pas +le module `minimum` dans notre crate +```bash +error[E0432]: unresolved import `crate::minimum` + --> src/something_or_nothing.rs:2:12 + | +2 | use crate::minimum::Minimum; + | ^^^^^^^ could not find `minimum` in the crate root + +For more information about this error, try `rustc --explain E0432`. +error: could not compile `part05` (lib) due to previous error +``` + +Pour la partie `2`, on a deux messages un peu différents +```bash + Compiling part05 v0.1.0 (/home/orestis/git/projects/rust-101/codes/rust_lang/part05) +warning: function `read_command_line` is never used + --> src/io.rs:2:8 + | +2 | pub fn read_command_line() -> [i32; crate::SIZE] { + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(dead_code)]` on by default + +warning: function `print_tab` is never used + --> src/io.rs:8:8 + | +8 | pub fn print_tab(tab: &[i32; crate::SIZE]) { + | ^^^^^^^^^ + +warning: `part05` (lib) generated 2 warnings +error[E0603]: module `io` is private + --> src/main.rs:2:13 + | +2 | use part05::io; + | ^^ private module + | +note: the module `io` is defined here + --> /home/orestis/git/projects/rust-101/codes/rust_lang/part05/src/lib.rs:9:1 + | +9 | mod io; + | ^^^^^^ + +For more information about this error, try `rustc --explain E0603`. +error: could not compile `part05` (bin "part05") due to previous error +``` +Le compilateur commence par nous prévenir par des *warnings* que les fonctions `print_tab` et `read_command_line()` ne sont jamais utilisées. Puis, nous avons un message nous prévenant que `io` est privé. + +### Visibilité + +Par défaut, Rust rend privées toutes les structures (et ses membres ou fonctions statiques), traits, fonctions, etc. Cela signifie qu'elles ne sont pas visibles en dehors du +module dans lequel elles sont définies. Pour les rendre visibles (en dehors du module dans lequel elles sont définies), il faut les rendre **publiques** à l'aide du préfixe `pub`. + +Il y a plusieurs exemples de l'utilisation dans ce chapitre. + +- Pour les fonctions: +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/io.rs:pub_fn}} +``` +on voit qu'on préfixe `pub` devant le mot-clé `fn` pour rendre la fonction publique. Si on le retire, le compilateur donnera le message d'erreur suivant +```bash +error[E0603]: function `print_tab` is private + --> src/main.rs:11:9 + | +11 | io::print_tab(&tab); + | ^^^^^^^^^ private function + | +note: the function `print_tab` is defined here + --> /home/orestis/git/projects/rust-101/codes/rust_lang/part05/src/io.rs:9:1 + | +9 | fn print_tab(tab: &[i32; crate::SIZE]) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For more information about this error, try `rustc --explain E0603`. +``` +- Pour un type énuméré: +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/something_or_nothing.rs:pub_enum}} +``` +où on préfixe le mot clé `enum` avec un `pub`. +- Pour les méthodes +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/something_or_nothing.rs:pub_method}} +``` +où comme pour les fonctions, on préfixe `fn` avec un `pub`. +- Pour les traits: +```rust,ignore +{{#include ../../codes/rust_lang/part05/src/minimum.rs:trait}} +``` +il faut noter que seule la définition du trait a besoin d'être publique. L'implémentation pour un type particulier n'a pas besoin de l'être. + +#### Remarque + +Tous les champs d'une structure (même publique) sont privés par défaut. Ainsi une structure `Point` contenant les coordonnées d'un points en deux dimension se définit comme +```rust,no_run +pub struct Point { + x: f32, + y: f32, +} +``` +En dehors du module où cette structure serait définie, il est impossible d'accéder aux champs de la structure. +L'instantiation et initialisation d'un `Point` +```rust,ignore +let p = Point { x: 1.0, y: 0.2 }; +``` +donnerait une erreur de compilation, car `x` et `y` sont privés. Il est donc nécessaire de préfixer les champs publics par un `pub` pour qu'ils soient accessible en dehors du module. +```bash +error[E0451]: field `x` of struct `Point` is private + --> src/main.rs:15:43 + | +15 | let p = something_or_nothing::Point { x: 1.0, y: 0.5 }; + | ^^^^^^ private field + +error[E0451]: field `y` of struct `Point` is private + --> src/main.rs:15:51 + | +15 | let p = something_or_nothing::Point { x: 1.0, y: 0.5 }; + | ^^^^^^ private field + +For more information about this error, try `rustc --explain E0451`. +``` +On aurait pour rendre `x` et `y` publics besoin de définir la structure comme +```rust,no_run +struct Point { + pub x: f32, + pub y: f32, +} +``` diff --git a/book/src/part06.md b/book/src/part06.md new file mode 100644 index 0000000..7484bc2 --- /dev/null +++ b/book/src/part06.md @@ -0,0 +1,305 @@ +# Discussion du code `part06` + +## Concepts + +Les concepts abordés dans cet exemple sont: + +1. [La documentation.](#la-documentation) +2. [Les tests.](#les-tests) +3. [Les outils en plus du compilateur](#les-outils-en-plus-du-compilateur) + +## Discussion + +Le Rust étant un langage moderne, il vient avec tout un tas de features qui sont très appréciables pour écrire du code robuste, propre et réutilisable. On va voir quelle est la syntaxe nécessaire pour écrire documentation et tests. + +### La documentation + +Il y a différents moyens de documenter un code. Pour un guide bien plus complet que ce qui est résumé ici, vous pouvez vous référer à [ce site](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html). + +#### Les commentaires + +Le plus simple est d'y ajouter des commentaires. En Rust, tous caractères qui suivent des `//` sur la même ligne sont considérés comme un commentaire (ignorés par le compilateur) + +```rust,no_run +// Ceci est un commentaire +// Et ceci un autre +let a = 2; // Ici on assigne 2 à a +``` + +On peut écrire également des commentaires sur plusieurs lignes sans avoir à mettre des `//` sur chacune. Pour ce faire on utilise la syntaxe `/* ... */` + +```rust,no_run +/* + Ceci est un commentaire + Et ceci un autre + Et en voici un dernier +*/ +let a = 2; /* Ici on assigne 2 à a */ +``` + +Ce type de documentation se prête très bien à des commentaires sur les détails du code, mais n'est pas très adapté à écrire une documentation plus générale sur le comportement du code. Ainsi, on a autre type de commentaires, qui seront utilisés pour générer automatiquement de la documentation à l'aide de l'outil `rustdoc`. + +#### La documentation par composants + +La documentation d'un composant, que ça soit une `struct`, un `enum`, une fonction, etc. se fait en préfixant `///` devant la ligne de documentation et la plaçant +directement au dessus du composant à commenter. Ainsi les exemples suivants permettent de: + +* Documenter une constante + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/lib.rs:size}} +``` + +* Documenter une fonction + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/io.rs:function}} +``` + +* Documenter une fonction statique + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/something_or_nothing.rs:static_function}} +``` + +* Documenter un trait + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/minimum.rs:minimum}} +``` + +* Documenter un type énuméré et ses variantes + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/something_or_nothing.rs:something_or_nothing}} +``` + +#### La documentation d'une `crate` et des modules + +Une librairie est appelée `crate` Rust. Afin de donner des informations de haut niveau +sur le fonctionnement d'une librairie, on peut utiliser une syntaxe spéciale `//!` à mettre au début de chaque +ligne de documentation (on peut également utiliser la syntaxe `/*! ... */`), comme ci-dessous + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/lib.rs:crate}} +``` + +Cette documentation se met dans le fichier `lib.rs` qui est également l'endroit où on importe les différents modules. + +On peut également documenter les modules individuellement. Pour ce faire, il faut utiliser la même syntaxe que pour la documentation de la crate, mais mettre cette documentation au début du fichier contenant chaque module comme par exemple au début du fichier `io.rs` + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/io.rs:io_module}} +``` + + +#### Markdown + +La documentation supporte la syntaxe du [Common Markdown](https://commonmark.org/) comme on peut le voir dans le code ci-dessus. On a en particulier +la possibilité de mettre des titres avec des `#` ou du code avec des code fences. Il est également possible de mettre des liens vers +d'autres parties de la documentation (avec les annotations tu type `[MyStruct]`) ce qui fait de la syntaxe un outil très puissant et intégré fortement au processus de développement. + +#### Génération de la documentation + +Tout ce travail d'annotation du code source permet d'utiliser `rustdoc` qui est un outil puissant de génération de documentation +sous la forme principal d'un site web. Dans le répertoire où se trouve le fichier `Cargo.toml`, on peut exécuter la commande +```bash +cargo doc +``` +et cela va générer la documentation dans un sous répertoire du projet. On peut également automatiquement ouvrir la +documentation dans un navigateur à l'aide de la commande +```bash +cargo doc --open +``` + +### Les tests + +La documentation aide grandement à la (ré-)utilisation d'une librairie et à son développement (collaboratif ou non). +Durant le processus de développement, il est également très utile (et important) d'écrire des tests pour le code. +Rust propose un framework de tests totalement intégré au langage. On peut ainsi très facilement écrire +des fonctions de test en ajoutant l'annotation `#[test]` directement au dessus de n'importe quelle fonction + +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/lib.rs:test_creation}} +``` + +La fonction `test_creation()` sera automatiquement appelée lors de l'appel à la commande +```bash +cargo test +``` +Aucune fonction non annotée par `#[test]` n'est appelée quand on exécute `cargo test`. +Si la fonction se termine sans erreur, le test est réussi (il est échoué si la fonction +s'arrête en cours de route). +On voit dans la fonction `test_creation()` l'appel à la macro `assert!()` qui prend en argument +une expression booléenne, ici `n1 == SomethingOrNothing::Nothing` dans le premier appel. +Si l'expression prend la valeur `true`, le code continue normalement son exécution, sinon +il fait appel à la macro `panic!()!` et l'exécution est interrompue et un code d'erreur est retourné par le programme. + +Dans l'appel à `n1 == SomethingOrNothing::Nothing`, on constate qu'on a besoin de vérifier +si la valeur `n1` est égale à `SomethingOrNothing::Nothing`. L'opérateur `==` n'est pas implémenté +pour les types complexes, ainsi le code suivant ne compile pas +```rust,compile_fail +enum SomethingOrNothing<T> { + Nothing, + Something(T), +} +fn main() { + let n1 = SomethingOrNothing::<i32>::Nothing; + let b = n1 == SomethingOrNothing::<i32>::Nothing; +} +``` +et nous donne un message d'erreur incluant +```bash +note: an implementation of `PartialEq<_>` might be missing for `SomethingOrNothing<i32>` +``` +Ainsi, on doit implémenter le trait `PartialEq` pour le type `SomethingOrNothing<T>`, +qui permet de tester l'égalité entre deux instances d'un type +```rust +{{#include ../../codes/rust_lang/part06/src/something_or_nothing.rs:something_or_nothing}} +{{#include ../../codes/rust_lang/part06/src/something_or_nothing.rs:partial_eq}} +fn main() { + let n1 = SomethingOrNothing::<i32>::Nothing; + assert!(n1 == SomethingOrNothing::<i32>::Nothing); +} +``` +On constate que dans notre implémentation, il est nécessaire que `T` implémente également +le trait `PartialEq`. Ainsi, deux `Nothing` sont égaux, un `Nothing` et un `Something` sont différents, et seulement quand les deux valeurs encapsulées dans deux `Something` sont égales +alors nous avons égalité. + +On peut également vouloir construire des tests qui échouent comme dans l'exemple ci-dessous +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/lib.rs:should_panic}} +``` +où on a annoté le test avec un `#[should_panic]`. Ce test, bien qu'il panique, sera considéré +comme réussi. Il faut néanmoins rester prudent avec ce type de test. Rien ne garantit que la fonction de test a paniqué au moment espéré. Le code pourrait tout à fait paniquer pour une raison autre que celle attendue. Cela est particulièrement vrai si le test est complexe. + +Finalement, il y a également la possibilité de regrouper les tests +comme ci-dessous (dans le fichier `lib.rs` dans cet exemple) +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/lib.rs:cfg_test}} +``` +Pour ce faire, il faut créer un module, (ici `mod tests`) et l'annoter avec une configuration spéciale `#[cfg(test)]`. Cela permet de séparer les tests totalement du reste du code et devoir +importer les différentes implémentations. + +Il est également possible de répartir les tests dans différents modules comme dans `minimum.rs` par exemple en plus de `lib.rs` +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/minimum.rs:cfg_test_min}} +``` + +### Tests de documentation + +Une bonne documentation inclut des exemples de code. Mais il n'y a rien de pire que des +exemples faux ou qui ne marchent pas. Ainsi, Rust offre la possibilité de compiler +et exécuter le code inclut dans la documentation: ces tests sont des **tests de documentation**. En effet, le code entre "code fences" +markdown sera compilé et exécuté (à moins qu'il soit annoté `ignore` où il sera pas compilé et `no_run` où il sera pas exécuté). Il y a deux exemples de tests de documentation dans notre code. Le premier concerne l'implémentation du trait +[Default](https://doc.rust-lang.org/std/default/trait.Default.html) pour `SomethingOrNothing` qui permet +de construire une instance par défaut qui sera la variante `Nothing` (voir `something_or_nothing.rs`). +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/something_or_nothing.rs:default}} +``` +On voit que pour que le test puisse compiler et s'exécuter il est nécessaire d'importer les bons modules/fonctions. Ici on importe +explicitement `part06::something_or_nothing::SomethingOrNothing` et on met les fonctions à exécuter dans un main. +Pour que ce "bruit" n'apparaisse pas dans la documentation, on préfixe les lignes par des `#`. Ainsi ces lignes +sont lues par le compilateur pour les tests de documentation mais sont ignorées lors du rendu de la documentation. + +Il y a également un exemple sur l'utilisation de la fonction `find_min()` (toujours dans `something_or_nothing.rs`) +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/something_or_nothing.rs:find_min}} +``` + +Finalement, les tests de documentation peuvent également être mis dans les documentation de module comme dans `minimum.rs` +```rust,ignore +{{#include ../../codes/rust_lang/part06/src/minimum.rs:min}} +``` + +#### Rapport sur l'exécution des tests + +Lors de l'appel à `cargo test` tous les tests sont exécutés et un rapport est généré. Sur +[ce code](#le-code), on obtient +```bash +$ cargo est + Compiling part06 v0.1.0 (/home/orestis/git/projects/rust-101/codes/rust_lang/part06) + Finished test [unoptimized + debuginfo] target(s) in 0.37s + Running unittests src/lib.rs (target/debug/deps/part06-f12750c4987ae624) + +running 5 tests +test test_creation ... ok +test minimum::tests::test_min_i32 ... ok +test tests::test_min ... ok +test tests::test_failure_creation - should panic ... ok +test tests::test_min_something_or_nothing ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Running unittests src/main.rs (target/debug/deps/part06-b63e62707c6aab7d) + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests part06 + +running 3 tests +test src/minimum.rs - minimum (line 8) ... ok +test src/something_or_nothing.rs - something_or_nothing::SomethingOrNothing<T> (line 38) ... ok +test src/something_or_nothing.rs - something_or_nothing::find_min (line 95) ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s +``` + +### Les outils en plus du compilateur + +La chaîne de compilation Rust vient avec deux autres outils très pratiques (en plus de `cargo` et `rustdoc`): `rustfmt` un formatteur de code, et `clippy` un linter. + +#### `rustfmt` + +Rust a fait le choix fasciste de définir des tas de conventions pour le nommage +des variables, des types, etc. L'outil `rustfmt` permet de formatter automatiquement +le code pour avoir un style uniforme au travers de tout votre code et ainsi améliorer +sa lisibilité. + +```rust +fn main() { + const size: usize = 9; + let tab: [i32; size] = [10, 32, 12, + 43, 52, 53, 83, 2, 9]; + + if (size == 0) + { + panic!("Size is of tab = 0."); + } + + println!("Among the numbers in the list:"); + for i in 0..size { + print!("{} ", tab[i]); + } + println!(); + + let mut MinTab=tab[0]; + for i in 1..size + { + if MinTab > tab[i] + { + MinTab=tab[i]; + } + } + + + + println!("The minimal value is: {}", MinTab); +} +``` + +Afin de le voir à l'oeuvre copier le code ci-dessus dans un `src/main.rs` et utilisez la commande `rustfmt --check src/main.rs` +et observez le résultat. Par défaut `rustfmt` va modifier le code, ainsi l'option `--check` va uniquement montrer +quelles sont les choses à corriger. En général, `rustfmt` est intégré avec les plugins dans des éditeurs de code +tels que `codium` qui l'intègre dans le plugin `rust-analyzer` par exemple. + +#### `clippy` + +L'outil `clippy` est un détecteur automatique de mauvaise pratiques de codage, telles que définies +par la communauté du Rust. Il permet d'écrire du code le plus idiomatique possible +et en général d'éviter certaines mauvaises pratiques et/ou de simplifier +le code avec des *patterns* connus. + +Comme ci-dessus, prenez ce code et exécutez la commande `cargo clippy` pour voir les recommandations du linter. diff --git a/codes/rust_lang/part00/src/main.rs b/codes/rust_lang/part00/src/main.rs index 2420ed2..094b36f 100644 --- a/codes/rust_lang/part00/src/main.rs +++ b/codes/rust_lang/part00/src/main.rs @@ -13,7 +13,8 @@ fn main() { const SIZE: usize = 9; let tab: [i32; SIZE] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; - if SIZE == 0 { + + if (SIZE == 0) { panic!("Size is of tab = 0."); } @@ -29,5 +30,6 @@ fn main() { min = tab[i]; } } + println!("The minimal value is: {}", min); } diff --git a/codes/rust_lang/part05/src/io.rs b/codes/rust_lang/part05/src/io.rs new file mode 100644 index 0000000..550c1f1 --- /dev/null +++ b/codes/rust_lang/part05/src/io.rs @@ -0,0 +1,15 @@ +// Poorly emulates the parsing of a command line. +pub fn read_command_line() -> [i32; crate::SIZE] { + [10, 32, 12, 43, 52, 53, 83, 2, 9] +} + +// Prints all the elements of the `tab`. +// Tab is borrowed here +// ANCHOR: pub_fn +pub fn print_tab(tab: &[i32; crate::SIZE]) { + for t in tab { + print!("{} ", t); + } + println!(); +} +// ANCHOR_END: pub_fn diff --git a/codes/rust_lang/part05/src/lib.rs b/codes/rust_lang/part05/src/lib.rs new file mode 100644 index 0000000..6be29aa --- /dev/null +++ b/codes/rust_lang/part05/src/lib.rs @@ -0,0 +1,12 @@ +// ANCHOR: lib_modules +/*! +Part05 illustrates the concepts of **modules** and **visibility**. +*/ + +// The size of the tab +const SIZE: usize = 9; + +pub mod io; +mod minimum; +pub mod something_or_nothing; +// ANCHOR_END: lib_modules diff --git a/codes/rust_lang/part05/src/main.rs b/codes/rust_lang/part05/src/main.rs index 936bf3c..7ecb8d7 100644 --- a/codes/rust_lang/part05/src/main.rs +++ b/codes/rust_lang/part05/src/main.rs @@ -1,212 +1,15 @@ -/*! -This is an example of Rust crate comments (or inner comments). -They will be rendered in the front page of your (crate) library. - -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/part04/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. -Each line written here could be prefixed by `//!` instead of enclosed in `/*! */`. - -For more informations about writing documentation [follow that link](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html). - -Also Rust comes with great tooling. -- Clippy: A linter. -- Rustfmt: A formatter. -*/ - -// Rust basics: -// - Tests -// - Copy/Clone via derive -// - PartialEq -// - Documentation -// - clippy, rustfmt - -/// An generic enumerated type that has two variants that are [Clone] -/// and [Copy] using derive. -/// -/// - Nothing -/// - Something -#[derive(Clone, Copy)] -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. - fn print(&self) { - match self { - SomethingOrNothing::Nothing => println!("Nothing."), - SomethingOrNothing::Something(val) => println!("Something is: {}", val), - } - } -} - -impl<T> Default for SomethingOrNothing<T> { - /// By Default a [SomethingOrNothing] is a nothing. - fn default() -> Self { - SomethingOrNothing::Nothing - } -} - -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, - } - } -} - -// If we remove Copy, we have a problem with the t in tab -// in the computation of the minimum. -trait Minimum: Copy { - fn min(self, rhs: Self) -> Self; -} - -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) - } - } - } -} - -// i32 is Copyable as a very basic type as f32, f64, etc. -// Arrays for example are not copyable. -impl Minimum for i32 { - fn min(self, rhs: Self) -> Self { - if self < rhs { - self - } else { - rhs - } - } -} - -const SIZE: usize = 9; - -/// Poorly emulates the parsing of a command line. -fn read_command_line() -> [i32; SIZE] { - [10, 32, 12, 43, 52, 53, 83, 2, 9] -} - -/// Prints all the elements of the `tab`. -/// Tab is borrowed here -fn print_tab(tab: &[i32; SIZE]) { - for t in tab { - print!("{} ", t); - } - println!(); -} - -/// 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 -/// -/// ``` -/// # fn main() { -/// let tab = [10, 32, 12, 43, 52, 53, 83, 2, 9]; -/// let min = find_min(&tab); -/// assert!(min == SomethingOrNothing::Something(2)); -/// # } -/// ``` -fn find_min<T: Minimum>(tab: &[T; SIZE]) -> 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: main_imports +use part05::io; +use part05::something_or_nothing::find_min; +// ANCHOR_END: main_imports fn main() { - let tab = read_command_line(); + // part05::io is imported but not read_command_line + let tab = io::read_command_line(); println!("Among the Somethings in the list:"); - print_tab(&tab); - // There are alternatives to access fields of tuples + // part05::io is imported but not print_tab + io::print_tab(&tab); + // part05::something_or_nothing::find_min is imported and can be used directly let min = find_min(&tab); - // The first field is not used therefore we can replace it with "_" min.print(); } - -#[test] -fn test_creation() { - let n1: SomethingOrNothing<i32> = SomethingOrNothing::default(); - assert!(n1 == SomethingOrNothing::Nothing); - let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1); - assert!(n2 == SomethingOrNothing::Something(1)); -} - -#[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)); -} - -#[cfg(test)] -mod tests { - #[test] - fn test_min() { - let x = 5; - let y = 10; - assert_eq!(x.min(y), x); - assert_eq!(y.min(x), x); - } - - #[test] - fn test_min_something_or_nothing() { - use crate::Minimum; - let x = crate::SomethingOrNothing::Something(5i32); - let y = crate::SomethingOrNothing::Something(10i32); - let z = crate::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/part05/src/minimum.rs b/codes/rust_lang/part05/src/minimum.rs new file mode 100644 index 0000000..63adf2d --- /dev/null +++ b/codes/rust_lang/part05/src/minimum.rs @@ -0,0 +1,15 @@ +// ANCHOR: trait +pub trait Minimum: Copy { + fn min(self, rhs: Self) -> Self; +} +// ANCHOR_END: trait + +impl Minimum for i32 { + fn min(self, rhs: Self) -> Self { + if self < rhs { + self + } else { + rhs + } + } +} diff --git a/codes/rust_lang/part05/src/something_or_nothing.rs b/codes/rust_lang/part05/src/something_or_nothing.rs new file mode 100644 index 0000000..ee814c9 --- /dev/null +++ b/codes/rust_lang/part05/src/something_or_nothing.rs @@ -0,0 +1,53 @@ +// ANCHOR: minimum +use crate::minimum::Minimum; +// ANCHOR_END: minimum + +// ANCHOR: pub_enum +#[derive(Clone, Copy)] +pub enum SomethingOrNothing<T> { + Nothing, + Something(T), +} +// ANCHOR_END: pub_enum + +// ANCHOR: pub_method +impl<T: std::fmt::Display> SomethingOrNothing<T> { + pub fn print(&self) { + match self { + SomethingOrNothing::Nothing => println!("Nothing."), + SomethingOrNothing::Something(val) => println!("Something is: {}", val), + } + } +} +// ANCHOR_END: pub_method + +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. +pub fn find_min<T: Minimum>(tab: &[T; crate::SIZE]) -> 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 +} diff --git a/codes/rust_lang/part06/src/io.rs b/codes/rust_lang/part06/src/io.rs index 8c14560..706e3c0 100644 --- a/codes/rust_lang/part06/src/io.rs +++ b/codes/rust_lang/part06/src/io.rs @@ -1,7 +1,15 @@ +// ANCHOR: io_module +//! Contains functions to interact with the user, either +//! by reading inputs from the terminal, either by writing values +//! in it. +//ANCHOR_END: io_module + +// ANCHOR: function /// Poorly emulates the parsing of a command line. pub fn read_command_line() -> [i32; crate::SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } +// ANCHOR_END: function /// Prints all the elements of the `tab`. /// Tab is borrowed here diff --git a/codes/rust_lang/part06/src/lib.rs b/codes/rust_lang/part06/src/lib.rs index e3b0224..9e2bee4 100644 --- a/codes/rust_lang/part06/src/lib.rs +++ b/codes/rust_lang/part06/src/lib.rs @@ -1,27 +1,69 @@ -/*! -Part06 illustrates the concepts of **Ownership** and **Borrowing**. It also -presents [Clone] and [Copy] traits through derive. -*/ +// ANCHOR: crate +//! 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/part06/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. +// ANCHOR_END: crate +// ANCHOR: size +/// The SIZE constant allows to use statically sized arrays const SIZE: usize = 9; +// ANCHOR_END: size +// ANCHOR: lib_modules pub mod io; -mod minimum; +pub mod minimum; pub mod something_or_nothing; +// ANCHOR_END: lib_modules +// ANCHOR: test_creation +#[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)); +} +// ANCHOR_END: test_creation + +// ANCHOR: cfg_test #[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::Nothing); - let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1); - assert!(n2 == SomethingOrNothing::Something(1)); - } - + // ANCHOR: should_panic #[test] #[should_panic] fn test_failure_creation() { @@ -29,6 +71,7 @@ mod tests { assert!(n2 == SomethingOrNothing::Nothing); assert!(n2 == SomethingOrNothing::Something(2)); } + // ANCHOR_END: should_panic #[test] fn test_min() { @@ -37,16 +80,6 @@ mod tests { assert!(min == SomethingOrNothing::Something(-1)); } - #[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::Something(5i32); @@ -59,3 +92,4 @@ mod tests { assert!(z.min(z) == z); } } +// ANCHOR_END: cfg_test diff --git a/codes/rust_lang/part06/src/main.rs b/codes/rust_lang/part06/src/main.rs index 5501e75..94a38ed 100644 --- a/codes/rust_lang/part06/src/main.rs +++ b/codes/rust_lang/part06/src/main.rs @@ -5,8 +5,6 @@ fn main() { let tab = io::read_command_line(); println!("Among the Somethings in the list:"); io::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(); } diff --git a/codes/rust_lang/part06/src/main_old.rs b/codes/rust_lang/part06/src/main_old.rs new file mode 100644 index 0000000..05ea985 --- /dev/null +++ b/codes/rust_lang/part06/src/main_old.rs @@ -0,0 +1,175 @@ +// ANCHOR: crate +//! 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/part06/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: A linter. +//! - Rustfmt: A formatter. +// ANCHOR_END: crate + +// ANCHOR: something_or_nothing +/// An generic enumerated type that has two variants that are [Clone] +/// and [Copy] using derive. +/// +/// - Nothing +/// - Something +#[derive(Clone, Copy)] +enum SomethingOrNothing<T> { + /// A [SomethingOrNothing::Nothing] + Nothing, + /// A [SomethingOrNothing::Something] encapsulating a T + Something(T), +} +// ANCHOR_END: something_or_nothing + +// ANCHOR: static_function +impl<T: std::fmt::Display> SomethingOrNothing<T> { + /// A static function that prints the content of a SomethingOrNothing. + fn print(&self) { + match self { + SomethingOrNothing::Nothing => println!("Nothing."), + SomethingOrNothing::Something(val) => println!("Something is: {}", val), + } + } +} +// ANCHOR_END: static_function + +// ANCHOR: default +impl<T> Default for SomethingOrNothing<T> { + /// By Default a [SomethingOrNothing] is a nothing. + fn default() -> Self { + SomethingOrNothing::Nothing + } +} +// ANCHOR_END: default + +// ANCHOR: partial_eq +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, + } + } +} +// ANCHOR_END: partial_eq + +// ANCHOR: minimum +/// The [Minimum] trait computes the minimum value between two values of a type +trait Minimum: Copy { + fn min(self, rhs: Self) -> Self; +} +// ANCHOR_END: minimum + +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) + } + } + } +} + +// Since i32 is [Copy] we don't need to explicitly implement it for i32 +impl Minimum for i32 { + fn min(self, rhs: Self) -> Self { + if self < rhs { + self + } else { + rhs + } + } +} + +// ANCHOR: size +/// A constant that is the size of +const SIZE: usize = 9; +// ANCHOR_END: size + +// ANCHOR: function +/// Poorly emulates the parsing of a command line. +fn read_command_line() -> [i32; SIZE] { + [10, 32, 12, 43, 52, 53, 83, 2, 9] +} +// ANCHOR_END: function + +/// Prints all the elements of the `tab`. +/// Tab is borrowed here +fn print_tab(tab: &[i32; SIZE]) { + for t in tab { + print!("{} ", t); + } + println!(); +} + +/// 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 +/// +/// ``` +/// # fn main() { +/// let tab = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_min(&tab); +/// assert!(min == SomethingOrNothing::Something(2)); +/// # } +/// ``` +fn find_min<T: Minimum>(tab: &[T; SIZE]) -> 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 +} + +fn main() { + let tab = read_command_line(); + println!("Among the Somethings in the list:"); + 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(); +} diff --git a/codes/rust_lang/part06/src/minimum.rs b/codes/rust_lang/part06/src/minimum.rs index 7d7a654..3d4bb15 100644 --- a/codes/rust_lang/part06/src/minimum.rs +++ b/codes/rust_lang/part06/src/minimum.rs @@ -1,8 +1,25 @@ -// If we remove Copy, we have a problem with the t in tab -// in the computation of the minimum. +// ANCHOR: min +//! 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 part06::minimum::Minimum; +//! let one = 1; +//! let two = 2; +//! assert!(Minimum::min(one, two) == one); +//! ``` +// ANCHOR_END: min + +// ANCHOR: minimum +/// The [Minimum] trait computes the minimum value between two values of a type pub trait Minimum: Copy { fn min(self, rhs: Self) -> Self; } +// ANCHOR_END: minimum impl Minimum for i32 { fn min(self, rhs: Self) -> Self { @@ -13,3 +30,20 @@ impl Minimum for i32 { } } } + +// ANCHOR: cfg_test_min +#[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); + } +} +// ANCHOR_END: cfg_test_min diff --git a/codes/rust_lang/part06/src/something_or_nothing.rs b/codes/rust_lang/part06/src/something_or_nothing.rs index 32b3007..2e94bac 100644 --- a/codes/rust_lang/part06/src/something_or_nothing.rs +++ b/codes/rust_lang/part06/src/something_or_nothing.rs @@ -1,5 +1,9 @@ +//! Contains the core logic of the library, allowing to tore generic values +//! (or their absence) and manipulate them. + use crate::minimum::Minimum; +// ANCHOR: something_or_nothing /// An generic enumerated type that has two variants. /// /// - Nothing @@ -11,7 +15,9 @@ pub enum SomethingOrNothing<T> { /// A [SomethingOrNothing::Something] encapsulating a T Something(T), } +// ANCHOR_END: something_or_nothing +// ANCHOR: static_function impl<T: std::fmt::Display> SomethingOrNothing<T> { /// A static function that prints the content of a SomethingOrNothing. pub fn print(&self) { @@ -21,14 +27,31 @@ impl<T: std::fmt::Display> SomethingOrNothing<T> { } } } +// ANCHOR_END: static_function +// ANCHOR: default +/// Implementation of the [Default] trait that creates a [SomethingOrNothing] +/// that is a `Nothing` variant. +/// +/// # Example +/// +/// ``` +/// # use part06::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 } } +// ANCHOR_END: default +// ANCHOR: partial_eq +/// 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) { @@ -40,7 +63,10 @@ impl<T: PartialEq> PartialEq for SomethingOrNothing<T> { } } } +// ANCHOR_END: partial_eq +/// 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) { @@ -60,6 +86,7 @@ impl<T: Minimum> Minimum for SomethingOrNothing<T> { } } +// ANCHOR: find_min /// 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. @@ -82,3 +109,4 @@ pub fn find_min<T: Minimum>(tab: &[T; crate::SIZE]) -> SomethingOrNothing<T> { } minimum } +// ANCHOR_END: find_min -- GitLab