diff --git a/PLANNING.md b/PLANNING.md new file mode 100644 index 0000000000000000000000000000000000000000..0257a27308c2bc119e23797a227941b1027e26c4 --- /dev/null +++ b/PLANNING.md @@ -0,0 +1,37 @@ +# Préquel + +- [] Installation du cours. + +# Jour + +- [] Lundi matin (9h-10h15, 10h45-12h): + - Vérification install, et Hello world. + - part00 à part01 +- [] Lundi après-midi (13h-14h15, 14h45-16h): + - part02 +- [] Mardi matin (9h-10h15, 10h45-12h): + - part03 +- [] Mardi après-midi (13h-14h15, 14h45-16h): + - part04 +- [] Mercredi matin (9h-10h15, 10h45-12h): + - part05, part06 +- [] Mercredi après-midi (13h-14h15, 14h45-16h): + - Gestion d'erreurs + - Lambda +- [] Jeudi matin (9h-10h15, 10h45-12h): + - Collections (Vec, String, HashMap) + - Itérateurs +- [] 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): + - Choix du projet et groupes + +# Soir + +Lundi (17h-21h): Vérification install, et Hello world & part00 à part01 +Mardi (17h-21h): part02 & part03 +Mercredi (17h-21h): part04, part 05 & part 06 +Jeudi (17h-21h): Gestion d'erreurs, Lambda, Collections & Itérateurs \ No newline at end of file diff --git a/README.md b/README.md index e003c31f848c97fec980a12c4f97b7fe40cb6935..a9030a7ac57813f3894ed6e63b8b2953f79e7743 100644 --- a/README.md +++ b/README.md @@ -182,12 +182,13 @@ Le cours théorique est découpé comme suit: 4. Ownership, Borrowing. 5. Modules et visibilité. 6. Tests, documentation, outils variés (rustfmt, clippy, etc). -7. Vec, Gestion d'erreurs (Option, Result). Intro basique à l'io -8. Itérateurs -9. String, -10. Smart pointeurs (Rc principalement) et mutabilité intérieure. -11. CLI, I/O. -12. Unsafe Rust, Box, etc. -13. FFI +7. Gestion d'erreurs (Option, Result). +8. Closures (Fonctions anonymes) +9. Collections (Vec, String, HashMap) +10. Itérateurs +11. Smart pointeurs (Rc principalement) et mutabilité intérieure. +12. CLI, I/O. +13. Unsafe Rust, Box, etc. +14. FFI <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />Ce(tte) œuvre est mise à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International</a>. diff --git a/book/src/part07.md b/book/src/part07.md index 1c0d27e7b00007f2e2aa98ff166ddf4fccd4c6c3..e005278c9d48bdde62c2e81e551b6e0cfa4db58e 100644 --- a/book/src/part07.md +++ b/book/src/part07.md @@ -1 +1,391 @@ # Discussion du code `part07` + +## Concepts + +Les concepts abordés dans cet exemple sont: + +- [Discussion du code `part07`](#discussion-du-code-part07) + - [Concepts](#concepts) + - [Documentation](#documentation) + - [Discussion](#discussion) + - [Les différents types d'erreurs](#les-différents-types-derreurs) + - [Erreurs rattrapables](#erreurs-rattrapables) + - [Erreurs irrattrapables](#erreurs-irrattrapables) + - [Le type Option](#le-type-option) + - [La macro panic!](#la-macro-panic) + - [Le type Result](#le-type-result) + - [L'opérateur ?](#lopérateur-) + +## Documentation + +Afin de compléter ce cours, je vous recommande la lecture des ressources suivantes : + +- [La gestion des erreurs en Rust](https://doc.rust-lang.org/book/ch09-00-error-handling.html) +- [Les options](https://doc.rust-lang.org/std/option) +- [Les résultats](https://doc.rust-lang.org/std/result) +- [La macro panic](https://doc.rust-lang.org/stable/std/macro.panic.html) +- [Le trait Try et l'opérateur ?](https://doc.rust-lang.org/stable/std/ops/trait.Try.html) + +## Discussion + +Lors de l'exécution d'un programme, il existe en général une multitude d'endroits où des erreurs peuvent se produire. Il est important de savoir identifier +les différents types d'erreurs, ainsi que les outils offerts par le langage Rust permettant de gérer ces erreurs. + +### Les différents types d'erreurs + +Il faut savoir différentier deux principaux types d'erreurs. + +#### Erreurs rattrapables + +Dans un premier temps, il y a les erreurs rattrapables ou prévues. +En général, ces erreurs surviennent lorsque l'utilisateur entre des données erronées. + +Nous avons par exemple : + +- Le chemin d'un fichier fourni par l'utilisateur qui n'existe pas +- Une entrée invalide, p.ex une lettre à la place d'un nombre +- De mauvais identifiants pour la connexion à un serveur + +Ces erreurs sont attendues, il est donc normal d'anticiper leurs occurences, et par conséquent, de s'y préparer. +Elles de ne devraient pas interrompre l'exécution du programme. + +Pour détecter ce type d'erreur, nous devons essayer de prévoir tous les scénarios possibles. +Si je demande à l'utilisateur d'entrer une valeur comprise entre 1 et 10, je pourrais par exemple obtenir : + +- Une valeur non-numérique +- Une valeur non comprise dans l'intervalle demandée +- L'absence de valeur +- Une erreur impossible à prévoir parce qu'on ne peut pas tout prévoir. +Le meilleur moyen de se prémunir de ce genre d'erreur, consiste à utiliser des types adaptés. + +Prenons par exemple la fonction : + +```rust,no_run +fn make_color(red:i32, green:i32, blue:i32, alpha:i32) -> i32 { + red << 24 | green << 16 | blue << 8 | alpha // Quel est le problème? +} +``` + +Cette fonction prend en argument une valeur par canal de couleur et construit +un entier représentant une couleur rgba 32 bit. +Le problème majeur dans cette fonction est le typage de nos arguments. Pour fabriquer une couleur rgba 32 bit, +il faut un octet par canal. Le type `i32` n'est donc pas approprié, il pourrait être la source d'erreurs. + +Pour éviter cela, on pourrait par exemple le remplacer par `u8` qui permet de représenter un octet. + +```rust,no_run +fn make_color(red:u8, green:u8, blue:u8, alpha:u8)->u32 { + (red as u32) << 24 | (green as u32) << 16 | (blue as u32) << 16 | (alpha as u32) +} +``` + +De cette manière, on évite les erreurs potentielles à la source. Vous noterez qu'il est nécessaire +de caster vos arguments, afin d'avoir une expression cohérente du point de vue des types. Notre code +est donc moins susceptible de contenir une erreur, mais en contrepartie, il est plus complexe. + +#### Erreurs irrattrapables + +Le deuxième type d'erreurs que l'on peut rencontrer sont les erreurs irrattrapables (les bugs par exemple). + +Lorsque de notre programme se trouve dans un état incohérent, il est nécessaire d’interrompre son exécution. +De manière générale, les bugs surviennent lorsque le développeur a fait une erreur dans son code. Il existe +plusieurs erreurs possibles. Nous avons par exemple des erreurs : + +- algorithmiques (ex: un faute logique dans la conception de l'algorithme, un cas pas couvert) +- du système (ex: la carte réseau n'est pas accessible) +- humaines (ex: le développeur a fait une faute d'inattention en écrivant son code) + +Il faut à tout prix éviter ces erreurs au maximum. Un programme qui interrompt constamment son exécution, +n'est pas un programme robuste et par conséquent, un programme qui n'est pas fiable. +La notion de fiabilité dépasse le cadre de ce cours, mais nous pouvons dire pour résumer qu'il existe plusieurs +domaines et donc plusieurs niveau d'attente en terme de fiabilité. Il arrive souvent qu'un jeu soit buggé dans ses +premières versions. En revanche, il est n'est pas acceptable que le pilote automatique d'un avion crash au en plein +milieu d'un vol. + +### Le type Option + +Historiquement, on représentait l'absence de valeur par une valeur dédiée (p.ex : une personne qui n'a pas +renseigné son âge, possède un âge de -1, ou alors l'utilisation du pointeur `NULL`). Bien que cette solution fonctionne, elle peut-être la source de +nombreuses erreurs et bugs en tout genre. Un type optionnel est une alternative moderne et plus robuste pour +nombreuses erreurs et bugs en tout genre. Les langages modernes gèrent ces cas à l'aide de leur système de typage permettant de meilleures vérifications à la compilation et à l'exécution ce qui les rend plus robustes: on parle de types *optionnels*. + +Il existe un grand nombre de fonctions qui ne retournent pas forcèment un résultat. On peut également penser à +des structures. Le caractère optionnel d'une valeur n'est pas forcèment une erreur, mais si cet aspect n'est +pas géré correctement, il peut mener à des erreurs. + +Nous pouvons par exemple vouloir représenter un utilisateur qui peut s'il le veut fournir sa date de naissance +et son adresse email. Prenons la structure suivante : + +```rust,ignore +struct User { + username: String, + birth_date: i32, + email: String, +} +``` + +Si on souhaite récupérer l'une de ces deux informations, on pourrait se retrouver dans un cas où l'utilisateur n'a pas +souhaité renseigner l'information désirée. C'est là qu'intervient le type `Option<T>`. Il permet de représenter une valeur +optionnelle. + +Voici sa déclaration d'après la documentation Rust : + +```rust +pub enum Option<T> { + None, + Some(T), +} +``` + +C'est tout simplement d'un type énuméré qui contient soit une valeur sous la forme `Some(ma_valeur)` ou pas de valeur `None`. Il s'agit de la version générique du type `NumberOrNothing` vu dans la [partie 2](./part02.md). + +Nous pouvons donc réecrire notre structure `User` de cette manière : + +```rust,ignore +struct User { + username: String, + birth_date: Option<i32>, + email: Option<String>, +} +``` + +Si nous reprenons notre exemple du minimum d'un tableau, nous pouvons écrire notre fonction de la manière suivante : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/find_minimum.rs:min_with_option}} +``` + +Ici on commence par instancier notre minimum à `None`, puis on compare itérativement notre minimum avec +les valeurs du tableau encapsulées dans une option avec `Some(*t)`. + +Pour comparer deux options entre elles, nous avons implémenté le trait minimum pour une `Option<T>`, où `T` +implémente le trait minimum. Ce qui nous donne : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/minimum.rs:min_for_option}} +``` + +On peut voir ici que l'on décompose notre option grâce au pattern matching de Rust. + +### La macro panic! + +Un programme peut être amené à s'arrêter de manière anormale. En `C`, on utilise la fonction `abort`. +Cette dernière va indiquer au processus parent ayant exécuté le programme qu'une erreur s'est produite et que +l'exécution ne peut pas se poursuivre. + +En Rust, il est possible de mettre fin au programme d'une manière similaire à +ce que l'on retrouve en `C` avec la fonction suivante : + +```rust,should_panic +use std::process; + +fn main() { + process::abort(); +} +``` + +Néanmoins, Rust étant un langage moderne, il possède la macro `panic!`. Cette maco ne va pas simplement interrompre +l'exécution du programme et signaler une erreur au processus appelant. Par défaut, +elle va également remonter la pile des appels de fonctions et libérer la mémoire au fur à mesure. + +Pour déclencher une panique du programme, il suffit d'appeler la macro avec un message en argument : + +```rust,should_panic +fn main() { + panic!("!!! Oups something went wrong !!!") +} +``` + +Si on exécute ce code, on obtient l'erreur suivante : + +```console + Compiling playground v0.0.1 (/playground) + Finished dev [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/playground` +thread 'main' panicked at '!!! Oups something went wrong !!!', src/main.rs:2:5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +On peut voir que la console nous indique que le fil d'exécution principal `main` a paniqué, et il nous affiche le message +que nous avons passé en argument à notre macro `panic!`. + +Si nous reprenons notre exemple du minimum d'un tableau, nous pouvons écrire notre fonction de la manière suivante : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/find_minimum.rs:min_with_panic}} +``` + +La première chose que nous pouvons noter avec notre fonction est le type de retour. En effet, nous ne retournons pas +une option, mais bien un élèment de type T. En effet, si on est **certain** que notre fonction va retourner un élément, +il n'y a pas de raison d'encapsuler ce retour dans une strucutre quelconque. + +Si **malgré** notre certitude, la fonction ne parvenait pas à produire de résultat, nous serions alors dans un cas indertimné, +et par conséquent il faut mettre fin à l'exécution du programme. Pour cela, on peut voir dans la dernière expression que +si le minimum n'a pas été trouvé `None => panic!("The array is empty"),`, alors le programme se termine sur une panic. + +Le message d'erreur passé en argument est remonté plus haut. Il est en effet possible d'intercepter les panic +grâce à la fonction `std::panic::catch_unwind` ou en rédéfissant manuellement le comportement de la macro `panic!` à l'aide +de la fonction `std::panic::set_hook`. Néanmoins, l'usage de ces fonctions devrait être limité à des cas très spécifiques +que nous ne couvrirons pas dans ce cours d'introduction au Rust. + +Le développeur néophyte pourrait être tenté de *simplifier* (dans le sens du nombre de caractères) son code en utilisant +uniquement la macro `panic!`. Cela va à l'encontre des bonnes pratiques du Rust et aura tendance à rendre +votre code peu compréhensible et défaillant. Je vous recommande cet excellent [article](https://doc.rust-lang.org/book/ch09-03-to-panic-or-not-to-panic.html) de la documentation Rust qui explique quand utiliser la macro `panic!` à bon escient. + +En résumé, la macro `panic` devrait être utilisé à des fins de debugging ou uniquement lorsque le programme rentre dans +un état indeterminé, c'est à dire un erreur irrattrapable. + +### Le type Result + +Le Rust offre une solution plus élégante que d'interrompre l'exécution du programme pour gérer les erreurs. Il s'agit +comme pour `Option<T>` d'un type énuméré. Voici sa définition : + +```rust +enum Result<T, E> { + Ok(T), + Err(E), +} +``` + +Le type `Result<T,E>` peut contenir soit une valeur attendue en cas de réussite sous la forme d'un `Ok(ma_valeur)`, ou +valeur permettant de renseigner l'erreur sur forme de `Err(mon_erreur)`. C'est un type plus complet qu'une simple +`Option<T>`, car en cas d'absence de valeur, nous pouvons indiquer la cause de l'erreur. + +Prenons par exemple la fonction suivante qui permet de télécharger une page WEB : + +```rust,ignore +fn get_html(uri: String) -> String +``` + +Un problème pourrait se produire lors du téléchargement. On pourrait donc écrire : + +```rust,ignore +fn get_html(uri: String) -> Option<String> +``` + +Si notre fonction nous retourne `None`, on se rend compte très vite que l'on a aucune information sur la +raison de l'absence de données (pas de connexion, mauvaise url, accès interdit, etc...). + +C'est là qu'intervient le type `Result<T,E>`, en cas d'absence de résultat de la fonction, nous allons pouvoir +comprendre la source de l'erreur et réagir réagir en conséquence. Nous pourrions donc écrire : + +```rust,ignore +enum DownloadError { + BadURL, + Forbidden, + ... +} +fn get_html(uri: String) -> Result<String, DownloadError> +``` + +Reprenons maintenant notre exemple du minimum d'un tableau. La première chose que nous pouvons faire est de définir +type représentant les erreurs que nous pourrions rencontrer en cherchant le minimum : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/find_minimum.rs:find_min_error}} +``` + +Ici nous envisageons deux scénarios pouvant provoquer une erreur : + +- Le minimum d'une liste vide +- Une éventuelle erreur que nous n'aurions pas encore prévu. Cette erreur est accompagnée d'un message +décrivant l'erreur. + +Une fois nos erreurs définies, nous pouvons passer à l'implémentation de notre fonction de recherche du minimum : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/find_minimum.rs:min_with_result}} +``` + +Cette fonction n'est pas très différente des précédentes. On remarque à la fin que pour former un `Result`, +si nous trouvons un minimum, nous l'encapsulons dans un `Ok(...)`, sinon nous retournons une erreur avec `Err(...)`. +Ici la seule erreur que nous retournons est la liste vide. + +Pour traiter ce résultat, nous pouvons faire du pattern matching : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/main.rs:parse_result}} +``` + +Ici nous avons trois cas : + +- le minimum a été trouvé, on l'affiche. +- la liste était vide, on affiche un message d'erreur. +- le minimum n'a pas pu être trouvé à cause d'une erreur non gérée, le programme va donc se terminer sur +une panic contenant le message d'erreur retourné par la fonction. + +Il est commun de rencontrer un usage pouvant mener facilement à des erreurs de `Result`. Prenons par exemple +`Result<T, String>` ou `Result<T, i32>`. En règle général, ces types concrets permettent de retourner +un message ou un code en cas d'erreur. Bien que cela ne pose pas de problème du point de vue purement fonctionel, +il rend aveugle le compilateur. En effet, en définissant les erreurs attendues avec un type énuméré, le compilateur +peut s'assurer que toutes les erreurs ont été prises en compte lors d'une décomposition de l'erreur. + +Prenons l'exemple suivant : + +```rust,ignore +fn foo() -> Result<i32, String>{ + // do stuff + match val { + 1 => Ok(42), + 2 => Err("Oupsi"), + 3 => Err("G phai une faute"), + _ => Err("Aie aie aie") + } +} +``` + +Imaginons que j'aie fait une faute dans mon message d'erreur. Mon message d'erreur est le seul moyen de différencier +les différentes erreurs possibles. En corrigeant ma faute, je dois maintenant mettre à jour **tout mon code**, ainsi +que le **code de tous ceux qui utilisent mon code**. Je peux également me tromper sur la casse de mon message d'erreur +la ponctuation etc... Le compilateur ne verra pas l'erreur et je peux passer des heures à chercher pourquoi mon +code ne marche pas. + +Il en va de même avec les codes d'erreurs numériques, il suffit que je veuille changer un seul code pour devoir à +nouveau mettre tout à jour. + +Il est donc fortement recommandé d'éviter cet usage du type `Result`. + +Néanmoins, l'usage de type énuméré pour la gestion des erreurs peut-être source de redondance. On se retrouve avec +des types qui représentent les mêmes erreurs avec des noms différents dans chaque crate. Pour éviter cela, il est +préférable de commencer par chercher si son erreur existe avant de créer une nouvelle erreur. Cela dépasse le cadre +de ce cours, mais sachez qu'il existe deux crates populaires qui peuvent vous aider pour la gestion d'erreur : + +- [anyhow](https://docs.rs/anyhow/latest/anyhow/) +- [thiserror](https://docs.rs/thiserror/latest/thiserror/) + +### L'opérateur ? + +Le language Rust offre un sucre syntaxique, afin de simplifier la gestion des options et des erreurs imbriquées. +L'opérateur `?` permet de récupérer la valeur contenue ou faire remonter l'erreur ou l'absence de valeur. On s'en sert +principalement pour les `Option` et les `Result`. Pour plus de détails sur l'interface `Try` qui permet d'utiliser +l'opérateur `?` sur un type quelconque, je vous recommande [la documentation](https://doc.rust-lang.org/std/ops/trait.Try.html). + +Prenons un exemple directement tiré de notre code : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/find_minimum.rs:min_two_tabs_hand}} +``` + +Cette fonction prends deux tableaux en argument et va chercher quelle est la valeur minimum globale. +Si on veut réutiliser le code que nous avons déjà écrit, nous avons une fonction qui retourne un +`Result` et qui nous donne la valeur minimale d'un tableau. Il nous suffit donc de chercher la valeur +minimale dans le premier tableau, puis dans le deuxième, et enfin retourner la plus petite des deux +valeurs. + +Seulement, ce n'est pas aussi simple puisque l'on obtient un `Result` pour chaque fonction, il faut +systèmatiquement verifier qu'aucune erreur s'est produite. Ici, si une erreur est survenue, nous +n'avons rien d'autre à faire que de retourner l'erreur telle quelle. + +C'est en réalité un cas de figure que l'on retrouve souvent. On peut voir dans notre code ci-dessus, +le code est répetitif et rends le code la fonction moins lisible. + +Avec l'opérateur `?` on peut simplement remplacer le test ainsi : + +```rust,ignore +{{#include ../../codes/rust_lang/part07/src/find_minimum.rs:min_two_tabs_qm}} +``` + +Ces deux fonctions font strictement la même chose. L'opérateur agit comme un sucre syntaxique qui permet +d'allèger l'écriture du code et ainsi augmenter sa lisibilité. En clair, si le résultat est `Ok(val)`, +l'expression retourne `val`, sinon la fonction se termine ici et retourne le résultat `Err(error)` contenu +dans le résultat. \ No newline at end of file diff --git a/codes/run_tests.sh b/codes/run_tests.sh index 004243133f23544a0a15a76b3337034228ea37a9..db25ede748c7da8382b957a17f9c4f9da3dbe018 100755 --- a/codes/run_tests.sh +++ b/codes/run_tests.sh @@ -4,12 +4,7 @@ make -C c_lang/min_list cd rust_lang for d in *; do echo ==================== Running cargo run for $d ==================== - if [ $d == 'part07' ] - then - cargo run --manifest-path $d/Cargo.toml < $d/numbers.txt - else - cargo run --manifest-path $d/Cargo.toml - fi + cargo run --manifest-path $d/Cargo.toml 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/part07/numbers.txt b/codes/rust_lang/part07/numbers.txt deleted file mode 100644 index 96c95dafa6dc00eb4f7fb393cdf44412e655b7a3..0000000000000000000000000000000000000000 --- a/codes/rust_lang/part07/numbers.txt +++ /dev/null @@ -1,5 +0,0 @@ -3478 -26 --127 -1287 -189 \ No newline at end of file diff --git a/codes/rust_lang/part07/src/find_minimum.rs b/codes/rust_lang/part07/src/find_minimum.rs new file mode 100644 index 0000000000000000000000000000000000000000..ae0bd089b09a8de93636a6c1df895af21d304605 --- /dev/null +++ b/codes/rust_lang/part07/src/find_minimum.rs @@ -0,0 +1,206 @@ +//! 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 + +use crate::minimum::Minimum; + +// ANCHOR: find_min_error +#[derive(PartialEq)] +pub enum FindMinError { + EmptyList, + UnsupportedError(String), +} +// ANCHOR_END: find_min_error + +use crate::find_minimum::FindMinError::EmptyList; + +/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. +/// Returns a [Option::Some] containing the the minimum value +/// or [Option::None] if no minimum value was found. +/// +/// # Example +/// +/// ``` +/// # use part07::find_minimum::{find_min_with_option}; +/// # fn main() { +/// let tab = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_min_with_option(&tab); +/// assert!(min == Some(2)); +/// # } +/// ``` +// ANCHOR: min_with_option +pub fn find_min_with_option<T: Minimum>(tab: &[T]) -> Option<T> { + let mut minimum = None; + // Here is T is Copyable. Which means that t is not moved in the loop + for t in tab { + minimum = Minimum::min(minimum, Some(*t)); + } + minimum +} +// ANCHOR_END: min_with_option + +/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. +/// Returns a [Result::Ok] containing the minimum value +/// or an [Result::Err] containing the error, if no minimum value was found. +/// +/// # Example +/// +/// ``` +/// # use part07::find_minimum::{find_min_with_result}; +/// # fn main() { +/// let tab = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_min_with_result(&tab); +/// assert!(min == Ok(2)); +/// # } +/// ``` +/// +/// ``` +/// # use part07::find_minimum::{find_min_with_result}; +/// # fn main() { +/// let tab : [i32; 0] = []; +/// let min = find_min_with_result(&tab); +/// assert!(min.is_err()); +/// # } +/// ``` +// ANCHOR: min_with_result +pub fn find_min_with_result<T: Minimum>(tab: &[T]) -> Result<T, FindMinError> { + let mut minimum = None; + // Here is T is Copyable. Which means that t is not moved in the loop + for t in tab { + minimum = Minimum::min(minimum, Some(*t)); + } + + match minimum { + Some(val) => Ok(val), + None => Err(EmptyList), + } +} +// ANCHOR_END: min_with_result + +/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. +/// Returns a T which is the minimum value +/// or panics if if no minimum value was found. +/// +/// # Example +/// +/// ``` +/// # use part07::find_minimum::{find_min_with_panic}; +/// # fn main() { +/// let tab = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let min = find_min_with_panic(&tab); +/// assert!(min == 2); +/// # } +/// ``` +/// +/// ```should_panic +/// # use part07::find_minimum::{find_min_with_panic}; +/// # fn main() { +/// let tab : [i32; 0] = []; +/// let _min = find_min_with_panic(&tab); +/// # } +/// ``` +// ANCHOR: min_with_panic +pub fn find_min_with_panic<T: Minimum>(tab: &[T]) -> T { + let mut minimum = None; + // Here is T is Copyable. Which means that t is not moved in the loop + for t in tab { + minimum = Minimum::min(minimum, Some(*t)); + } + + // We decide that we cannot compute the minimum of an empty array + match minimum { + Some(val) => val, + None => panic!("The array is empty"), + } +} +// ANCHOR_END: min_with_panic + +/// Computes the minimum amongst two Arrays of a type T which implements the [Minimum] trait. +/// Returns a [Result::Ok] containing the minimum value +/// or an [Result::Err] containing the error, if no minimum value was found. +/// +/// We deal with errors in underlying function calls without the [?] operator. +/// +/// # Example +/// +/// ``` +/// # use part07::find_minimum::{find_min_amongst_arrays_by_hand}; +/// # fn main() { +/// let tab_a = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let tab_b = [22, 34, 11, 4, 52, 99, 71, 13, 43]; +/// let min = find_min_amongst_arrays_by_hand(&tab_a, &tab_b); +/// assert!(min == Ok(2)); +/// # } +/// ``` +/// +/// ``` +/// # use part07::find_minimum::{find_min_amongst_arrays_by_hand}; +/// # fn main() { +/// let tab_a = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let tab_b : [i32; 0] = []; +/// let min = find_min_amongst_arrays_by_hand(&tab_a, &tab_b); +/// assert!(min.is_err()); +/// # } +/// ``` +// ANCHOR: min_two_tabs_hand +pub fn find_min_amongst_arrays_by_hand<T: Minimum>( + lhs: &[T], + rhs: &[T], +) -> Result<T, FindMinError> { + let min_result = find_min_with_result(lhs); + let min_l = if let Ok(x) = min_result { + x + } else { + // Since tmp is not Ok, we return the error to the caller + return min_result; + }; + + let min_result = find_min_with_result(rhs); + let min_r = if let Ok(x) = min_result { + x + } else { + // Since tmp is not Ok, we return the error to the caller + return min_result; + }; + + Ok(min_l.min(min_r)) +} +// ANCHOR_END: min_two_tabs_hand + +/// Computes the minimum amongst two Arrays of a type T which implements the [Minimum] trait. +/// Returns a [Result::Ok] containing the minimum value +/// or an [Result::Err] containing the error, if no minimum value was found. +/// +/// We deal with errors in underlying function with the [?] operator. +/// +/// # Example +/// +/// ``` +/// # use part07::find_minimum::{find_min_amongst_arrays_qm_op}; +/// # fn main() { +/// let tab_a = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let tab_b = [22, 34, 11, 4, 52, 99, 71, 13, 43]; +/// let min = find_min_amongst_arrays_qm_op(&tab_a, &tab_b); +/// assert!(min == Ok(2)); +/// # } +/// ``` +/// +/// ``` +/// # use part07::find_minimum::{find_min_amongst_arrays_qm_op}; +/// # fn main() { +/// let tab_a = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +/// let tab_b : [i32; 0] = []; +/// let min = find_min_amongst_arrays_qm_op(&tab_a, &tab_b); +/// assert!(min.is_err()); +/// # } +/// ``` +// ANCHOR: min_two_tabs_qm +pub fn find_min_amongst_arrays_qm_op<T: Minimum>(lhs: &[T], rhs: &[T]) -> Result<T, FindMinError> { + // The question mark operator will unpack the value if the function returns [Result::Ok] + // or end the function and return the [Result:Err] to the caller. + let min_l = find_min_with_result(lhs)?; + let min_r = find_min_with_result(rhs)?; + + Ok(min_l.min(min_r)) +} +// ANCHOR_END: min_two_tabs_qm diff --git a/codes/rust_lang/part07/src/io.rs b/codes/rust_lang/part07/src/io.rs index 2eeaeb4f0201bac3b31daede9f1f2ffb2c0444d2..7a495499e8a65204e982f16093fabdf1cfb5251a 100644 --- a/codes/rust_lang/part07/src/io.rs +++ b/codes/rust_lang/part07/src/io.rs @@ -1,35 +1,23 @@ -use std::io::BufRead; +//! Contains functions to interact with the user, either +//! by reading inputs from the terminal, either by writing values +//! in it. -/// Reads i32 from the command line and returns a [Vec] containing -/// these numbers. Returns errors when the parsing fails. -pub fn read_command_line() -> Result<Vec<i32>, String> { - let mut v = Vec::new(); - let stdin = std::io::stdin(); - println!("Enter a list of numbers, one per line. End with Ctrl-D (Linux) or Ctrl-Z (Windows)."); - - for line in stdin.lock().lines() { - let line = match line { - Ok(l) => l, - Err(_) => { - return Err(String::from("Could not read line")); - } - }; +/// Poorly emulates the parsing of a command line. +pub fn read_command_line_correct() -> [i32; 9] { + [10, 32, 12, 43, 52, 53, 83, 2, 9] +} - match line.trim().parse::<i32>() { - Ok(num) => v.push(num), - Err(_) => { - return Err(String::from("Could not parse integer")); - } - } - } - Ok(v) +/// Poorly emulates the parsing of a command line. +pub fn read_empty_command_line() -> [i32; 0] { + [] } /// Prints all the elements of the `tab`. /// Tab is borrowed here -pub fn print_tab(tab: &Vec<i32>) { +pub fn print_tab(tab: &[i32]) { + print!("[ "); for t in tab { print!("{} ", t); } - println!(); + println!("]"); } diff --git a/codes/rust_lang/part07/src/lib.rs b/codes/rust_lang/part07/src/lib.rs index dd53b82629e1b15c144969d81fc4e6c075d2e4ff..e6c7da45ca41ed5d6832544c8013993a0f65d1da 100644 --- a/codes/rust_lang/part07/src/lib.rs +++ b/codes/rust_lang/part07/src/lib.rs @@ -1,59 +1,87 @@ -/*! -part07 illustrates the use of [Vec] and the Error Handling with [Option] and [Result]. -It also showcases struct enums. -*/ +//! 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_minimum; pub mod io; -mod minimum; -pub mod something_or_nothing; +pub mod minimum; #[cfg(test)] mod tests { - use crate::minimum::Minimum; - use crate::something_or_nothing::{find_min, SomethingOrNothing}; + use crate::find_minimum::{ + find_min_amongst_arrays_by_hand, find_min_amongst_arrays_qm_op, find_min_with_option, + find_min_with_panic, find_min_with_result, + }; + const TAB: [i32; 9] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; + const TAB_B: [i32; 9] = [22, 34, 11, 4, 52, 99, 71, 13, 43]; + const TAB_EMPTY: [i32; 0] = []; + const MIN_TAB: i32 = 2; #[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)); + fn test_find_min_option() { + let min = find_min_with_option(&TAB); + + assert!(min == Some(MIN_TAB)); + } + + #[test] + fn test_find_min_option_empty() { + let min = find_min_with_option(&TAB_EMPTY); + + assert!(min.is_none()); + } + + #[test] + fn test_find_min_result() { + let min = find_min_with_result(&TAB); + + assert!(min == Ok(MIN_TAB)); + } + + #[test] + fn test_find_min_result_empty() { + let min = find_min_with_result(&TAB_EMPTY); + + assert!(min.is_err()); + } + + #[test] + fn test_find_min_panic() { + let min = find_min_with_panic(&TAB); + + assert!(min == MIN_TAB); } #[test] #[should_panic] - fn test_failure_creation() { - let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1); - assert!(n2 == SomethingOrNothing::default()); - assert!(n2 == SomethingOrNothing::new(2)); + fn test_find_min_panic_empty() { + let _min = find_min_with_panic(&TAB_EMPTY); + } + + #[test] + fn test_find_min_amongst_arrays_bh() { + let min = find_min_amongst_arrays_by_hand(&TAB, &TAB_B); + + assert!(min == Ok(MIN_TAB)); } #[test] - fn test_min() { - let a = vec![1, 5, -1, 2, 0, 10, 11, 0, 3]; - let min = find_min(&a); - assert!(min == SomethingOrNothing::new(-1)); + fn test_find_min_amongst_arrays_qm() { + let min = find_min_amongst_arrays_qm_op(&TAB, &TAB_B); + + assert!(min == Ok(MIN_TAB)); } #[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); + fn test_find_min_amongst_arrays_bh_empty() { + let min = find_min_amongst_arrays_by_hand(&TAB, &TAB_EMPTY); + + assert!(min.is_err()); } #[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); + fn test_find_min_amongst_arrays_qm_empty() { + let min = find_min_amongst_arrays_qm_op(&TAB, &TAB_EMPTY); + + assert!(min.is_err()); } } diff --git a/codes/rust_lang/part07/src/main.rs b/codes/rust_lang/part07/src/main.rs index 8ee8b45dded0909740a51df45e36af5ef513f0c8..a967099a3b0485ec23964110b9fb6b1ebc85faa4 100644 --- a/codes/rust_lang/part07/src/main.rs +++ b/codes/rust_lang/part07/src/main.rs @@ -1,14 +1,41 @@ +use part07::find_minimum::{ + find_min_amongst_arrays_qm_op, find_min_with_option, find_min_with_result, + FindMinError::EmptyList, FindMinError::UnsupportedError, +}; use part07::io; -use part07::something_or_nothing::find_min; - -fn main() -> Result<(), String> { - let tab = match io::read_command_line() { - Ok(tab) => tab, - Err(s) => return Err(s), - }; - println!("Among the Somethings in the list:"); + +fn main() { + let tab = io::read_command_line_correct(); + println!("Among the elements in the list:"); + io::print_tab(&tab); + let min = find_min_with_option(&tab); + match min { + Some(val) => print!("The minimum value is {}", val), + None => eprintln!("There is no minimum since the list is empty"), + } + println!(""); + println!(""); + + let tab_empty = io::read_empty_command_line(); + println!("Among the elements in the list:"); + io::print_tab(&tab_empty); + + //ANCHOR: parse_result + let min = find_min_with_result(&tab_empty); + match min { + Ok(val) => print!("The minimum value is {}", val), + Err(EmptyList) => eprintln!("The array is empty"), + Err(UnsupportedError(msg)) => panic!("Unsupported error : {}", msg), + } + //ANCHOR_END: parse_result + + println!("Among the elements in the lists:"); io::print_tab(&tab); - let min = find_min(&tab); - min.print(); - Ok(()) + io::print_tab(&tab_empty); + let min = find_min_amongst_arrays_qm_op(&tab, &tab_empty); + match min { + Ok(val) => print!("The minimum value is {}", val), + Err(EmptyList) => eprintln!("One or both arrays are empty"), + Err(UnsupportedError(msg)) => panic!("Unsupported error : {}", msg), + } } diff --git a/codes/rust_lang/part07/src/minimum.rs b/codes/rust_lang/part07/src/minimum.rs index 7d7a654b8f2c43bc76e35616813465c6142a0bff..222b12c33045f361977066521ff2b7206b0bed9c 100644 --- a/codes/rust_lang/part07/src/minimum.rs +++ b/codes/rust_lang/part07/src/minimum.rs @@ -1,5 +1,18 @@ -// If we remove Copy, we have a problem with the t in tab -// in the computation of the minimum. +//! 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 part07::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; } @@ -13,3 +26,35 @@ impl Minimum for i32 { } } } + +// ANCHOR: min_for_option +impl<T: Minimum> Minimum for Option<T> { + fn min(self, rhs: Self) -> Self { + match self { + Some(val_l) => Some(match rhs { + Some(val_r) => val_l.min(val_r), + None => val_l, + }), + None => match rhs { + Some(val_r) => Some(val_r), + None => None, + }, + } + } +} +// ANCHOR_END: min_for_option + +#[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/part07/src/something_or_nothing.rs b/codes/rust_lang/part07/src/something_or_nothing.rs deleted file mode 100644 index 600bf0c6dbfbc567350a0ce74d4885bc4d07d054..0000000000000000000000000000000000000000 --- a/codes/rust_lang/part07/src/something_or_nothing.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::fmt::Display; - -use crate::minimum::Minimum; - -/// An generic enumerated type that encapsulates and Option<T>. -#[derive(Clone, Copy)] -pub struct SomethingOrNothing<T>(Option<T>); - -impl<T: Minimum + Display> SomethingOrNothing<T> { - pub fn new(val: T) -> Self { - SomethingOrNothing(Some(val)) - } - /// A static function that prints the content of a SomethingOrNothing. - pub fn print(&self) { - match self.0 { - None => println!("Nothing."), - Some(val) => println!("Something is: {}", val), - } - } -} - -impl<T> Default for SomethingOrNothing<T> { - /// By Default a [SomethingOrNothing] is a nothing. - fn default() -> Self { - SomethingOrNothing(None) - } -} - -impl<T: PartialEq + Minimum> PartialEq for SomethingOrNothing<T> { - fn eq(&self, other: &Self) -> bool { - match (self.0, other.0) { - (None, None) => true, - (Some(lhs), Some(rhs)) => lhs == rhs, - _ => false, - } - } -} - -impl<T: Minimum + Display> Minimum for SomethingOrNothing<T> { - fn min(self, rhs: Self) -> Self { - match (self.0, rhs.0) { - (None, None) => SomethingOrNothing(None), - (Some(lhs), Some(rhs)) => SomethingOrNothing::new(lhs.min(rhs)), - (None, Some(rhs)) => SomethingOrNothing::new(rhs), - (Some(lhs), None) => SomethingOrNothing::new(lhs), - } - } -} - -/// Computes the minimum of an Array of a type T which implements the [Minimum] trait. -/// Returns a [Some] containing the the minimum value -/// or [None] if no minimum value was found. -/// -/// # Examples -/// -/// ``` -/// # use part07::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::new(2)); -/// # } -/// ``` -/// -/// ``` -/// # use part07::something_or_nothing::{SomethingOrNothing, find_min}; -/// # fn main() { -/// let tab: Vec<i32> = vec![]; -/// let min = find_min(&tab); -/// assert!(min == SomethingOrNothing::default()); -/// # } -/// ``` -pub fn find_min<T: Minimum + Display>(tab: &Vec<T>) -> SomethingOrNothing<T> { - let mut minimum: SomethingOrNothing<T> = SomethingOrNothing(None); - // Here is T is Copyable. Which means that t is not moved in the loop - for t in tab { - minimum = minimum.min(SomethingOrNothing::new(*t)); - } - minimum -}