Skip to content
Snippets Groups Projects
Commit de732d1b authored by Michaël El Kharroubi's avatar Michaël El Kharroubi :satellite: Committed by orestis.malaspin
Browse files

Errors management

parent ce5afded
Branches
No related tags found
No related merge requests found
# 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
......@@ -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>.
# 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
......@@ -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
echo ==================== Running cargo test for $d ====================
cargo test --manifest-path $d/Cargo.toml --workspace --verbose
echo ==================== Running cargo doc for $d ====================
......
3478
26
-127
1287
189
\ No newline at end of file
//! 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
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!("]");
}
/*!
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());
}
}
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(&tab);
min.print();
Ok(())
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);
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),
}
}
// 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);
}
}
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
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment