Skip to content
Snippets Groups Projects
Commit 14b2b42b authored by orestis.malaspin's avatar orestis.malaspin
Browse files

Merge branch 'collections' into 'main'

Adds collections: String and Vec

See merge request orestis.malaspin/rust-101!58
parents bc9e6348 e75a7917
Branches
No related tags found
1 merge request!58Adds collections: String and Vec
Pipeline #26184 passed
Showing
with 1422 additions and 2 deletions
......@@ -14,5 +14,7 @@
- [Part 09](./part09.md)
- [Part 10](./part10.md)
- [Part 11](./part11.md)
- [Les collections](./collections.md)
- [Lifetimes](./lifetimes.md)
- [CLI](./cli.md)
- [Part 12](./part12.md)
# Les collections
## Concepts
Les concepts abordés dans cet exemple sont:
- [Les collections](#les-collections)
- [Concepts](#concepts)
- [Documentation](#documentation)
- [Discussion](#discussion)
- [Le type `Vec`](#le-type-vect)
- [Le type `String`](#le-type-string)
- [Les slices](#les-slices)
## Documentation
Afin de compléter ce cours, je vous recommande la lecture des ressources suivantes :
- [Les Vec](https://doc.rust-lang.org/book/ch08-01-vectors.html)
- [Les String](https://doc.rust-lang.org/book/ch08-02-strings.html)
- [Les slices](https://doc.rust-lang.org/book/ch04-03-slices.html)
## Discussion
En Rust, comme dans la plupart des langages modernes, il existe des structures de données qui permettent
de se simplifier la vie lors de l'implémentation de divers algorithmes.
Dans ce chapitre, nous allons discuter des types `Vec<T>`, `String`, des slices.
Dans ce code nous modifions que très peu le code [de la partie 6](part06.md) afin
d'utiliser les types `Vec<i32>`, `String` et les slices.
### Le type `Vec<T>`
Le type `Vec<T>` est une collection qui permet de stocker des données d'un type unique générique, `T`.
Ces données sont stockées dans un espace mémoire qui est garanti d'être contigu. Cet espace mémoire
peut changer dynamiquement à l'exécution contrairement aux tableaux statiques. D'un certain point
de vue, on peut le considérer comme un "pointeur intelligent": un `Vec` est un pointeur sur le tas,
qui a une certaine capacité mémoire et sait combien la mémoire sur laquelle il pointe
est pleine.
Dans notre code, nous avons créé une fonction `read_command_line()`
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:read_command_line}}
```
Ici, un `Vec<i32>` est instancié et on ajoute des éléments aléatoires dedans à l'aide de la
crate `rand()`.
Pour créer un `Vec` vide, on utilise la fonction associée, qui crée un vecteur vide
```rust
{{#include ../../codes/rust_lang/collections/src/io.rs:vec_new}}
```
Ensuite, on remplit le vecteur avec des nombres aléatoires à l'aide
d'une boucle `for`
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:vec_for}}
```
qui itère sur les indices allant de `0` à `len-1`.
Comme, nous n'utilisons pas l'indice, nous l'ignorons à l'aide de l'annotation `_i`.
On ajoute des éléments dans le vecteur avec la fonction `push()` et comme nous modifions
`v` il est primordial de noter qu'il est `mut`-able. Le générateur de nombre aléatoire
est stocké dans une variable `rng` qui doit elle aussi être mutable.
En effet, les générateurs de nombres aléatoires stockent en général un état interne.
### Le type `String`
Une `String` ou un chaîne de caractère, est une séquence de caractères `UTF-8`.
On pourrait penser naïvement que ce n'est rien d'autre qu'un `Vec<char>`: en fait c'est plus compliqué que cela.
Bien que la chaîne de caractères, soit également rien d'autre qu'un pointeur vers des données sur le tas,
ainsi qu'une variable contenant la capacité de la mémoire sur laquelle pointe le pointeur et son remplissage.
Le problème principal vient de l'encodage `UTF-8`: c'est un encodage de taille variable qui englobe les caractères
ASCII (qui sont stockés sur un octet) et une très grande quantité d'autres caractères (l'alphabet grec, des emojis, etc.)
qui sont stockés sur 1 à 4 octets. Ainsi chaque lettre de la chaîne de caractère correspond à un nombre variable d'octets
(ce qui n'est pas le cas pour un `Vec`). Il est donc très vivement **déconseillé** de tenter d'indexer
une `String` (faire `s[i]`) comme on le ferait avec un `Vec`. Il faut plutôt utiliser la méthode `.get(i)` qui
interprète les caractères en fonction de la longueur de leurs encodages.
Une illustration de l'utilisation d'une chaîne de caractère se trouve à la fonction
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:read_command_line_str}}
```
Cette fonction construit une chaîne de caractères constituées de nombres et d'espaces,
puis la transforme en `Vec<i32>` dont on calculera ensuite le minimum.
Une `String` est souvent construite à partir d'une "chaîne littérale" à l'aide du trait de conversion `From`
(on les reconnaît parce qu'elles sont entourées de guillemets, `""`)
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:from}}
```
dont le type est `str` formellement (c'est une chaîne de caractères qui vit durant toute la durée de vie du programme).
Néanmoins, le type `str` n'est jamais utilisé en tant que tel en Rust, mais on utilise plutôt son "slice"
`&str` (plus sur les "tranches" dans la [section suivante](#les-slices)).
Nous voyons que dans le code ci-dessus nous avons déclaré la variable `s` comme étant mutable, car ensuite nous ajoutons
une slice de chaîne de caractère (de type `&str`) à l'aide de la fonction `push_str()`
dans `s`
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:push_str}}
```
On peut également ajouter des `char` (ils sont entourés d'apostrophes `' '`) à l'aide de la fonction `push()`
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:push_char}}
```
Ensuite cette chaîne de caractères est convertie en `Vec<&str>` où chaque élément du `Vec` est un mot, qui est une sous chaîne de `s`.
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:split}}
```
Finalement, dans
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:string_for}}
```
on crée un nouveau `Vec<i32>` dans lequel on ajoute les mots convertis en entiers.
On commence par itérer sur le `Vec<&str>` en utilisant les indices de `0` à `s.len()-1` (la longueur du `Vec` `s`).
Puis nous passons à la conversion à proprement parler, bien qu'on fasse des choses un peu compliquées
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:conversion}}
```
Ici, on commence par récupérer le `i`-ème index de `s` à l'aide de la méthode `get(i)` qui retourne
une `Option<&str>` (si `i` est un indice valide nous avons `Some(s[i])`, sinon `None`). Puis, nous transformons
l'option avec `ok_or()` (nous encapsulons `s[i]` dans un `Ok()` si nous avons un `Some()` et transformons
`None`en `Err("Unable to index")`). Ensuite nous "parsons" `s[i]` et retournons une erreur si le parsing échoue.
Si tout s'est bien passé nous faisons donc un `push()` de chaque `i32` et finissons par retourner le `Vec<i32>`
encapsulé dans un `Ok()`.
### Les slices
Un slice est une "tranche" de tableau, statique ou dynamique: une référence vers un bout de mémoire et
la longueur de cette mémoire.
Ainsi, si nous créons un tableau statique, nous pouvons référencer une "tranche" ou slice
avec la syntaxe suivante
```rust
let a = [1, 2, 3, 4, 5, 6, 7, 8];
let b = &a[1..4]; // on pointe vers [2, 3, 4]
```
`b` sera donc une référence et saura que la mémoire sur laquelle elle pointe est de longueur `3`
(cette information permet d'éviter les dépassements de capacité).
On notera la syntaxe `x..y``y` est non inclus (comme pour la boucle `for` avec les indices).
Il existe également une syntaxe sans bornes à gauche, à droite, ou à gauche et à droite.
```rust
let a = [1, 2, 3, 4, 5, 6, 7, 8];
let b = &a[1..]; // on pointe vers [2, 3, .., 8]
let b = &a[..5]; // on pointe vers [1, 2, .., 5]
let b = &a[..]; // on pointe vers [1, 2, .., 8]
```
Cette syntaxe s'applique également pour toute collection qu'on peut indexer
Le type d'un slice est noté par `&[T]`, où `T` est un type. On en voit un exemple
lorsqu'on veut afficher un tableau par exemple
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/io.rs:print_tab}}
```
ou encore dans la fonction `find_min()`
```rust,ignore
{{#include ../../codes/rust_lang/collections/src/something_or_nothing.rs:find_min}}
```
Comme on le voit dans le `main()` l'implémentation à l'aide d'un slice dans les fonction
permet une bien plus grande généricité que si on impose un type `Vec`, un tableau statique,
ou un slice.
```rust,ignore
// Vec
{{#include ../../codes/rust_lang/collections/src/main.rs:vec}}
// Slice
{{#include ../../codes/rust_lang/collections/src/main.rs:ref}}
// Array
{{#include ../../codes/rust_lang/collections/src/main.rs:tab}}
```
La notation `&str` représente ainsi une référence vers un `str` qui est une chaîne de caractères
littérale, allouée pour la durée entière d'un programme dans une zone dédiée de la mémoire.
Le type `str` étant "immovable" il n'est jamais utilisé tel quel, mais uniquement via des références.
Comme pour le slice utilisé pour généraliser le passage en argument des tableaux,
le slice de string `&str` est également utilisé pour généraliser le passage de argument
de chaînes de caractères.
## Rustlings
Les rustlings à faire dans ce chapitre sont les suivants:
### Les `Vec`
```bash
$ rustlings run vecs1
$ rustlings run vecs2
```
### Les `String`
```bash
$ rustlings run strings1
$ rustlings run strings2
$ rustlings run strings3
$ rustlings run strings4
```
# Lifetimes
## Concepts
Les concepts abordés dans cet exemple sont:
1. [Le pattern `NewYtpe`](#le-pattern-newtype)
2. [Les lifetimes](#les-lifetimes)
3. [Les itérateurs et la généricité]()
Pour plus d'informations sur le pattern `NewType`, vous pouvez vous référer aux
chapitres [19.2](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) et [19.3](https://doc.rust-lang.org/book/ch19-04-advanced-types.html) du livre.
Pour les lifetimes, il y a le [chapitre du livre](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) correspondant,
ainsi que celui du [Rustonomicon](https://doc.rust-lang.org/nomicon/lifetimes.html).
## Discussion
Dans ce chapitre, nous allons voir principalement deux concepts différents qui sont importants en Rust
et qu'on retrouve dans beaucoup de code. Les lifetimes, en particulier, sont un sujet complexe
et on verra deux applications différentes mais cela constitue la pointe de l'iceberg des applications
possibles. Le pattern `NewType` est lui bien plus simple, et ne nécessite pas une très longue discussion.
### Le pattern `NewType`
Le pattern `NewType` très commun en Rust consiste à encapsuler un type externe à une crate, dans un type local
comme dans
```rust
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype}}
```
où on encapsule le type externe `Option<T>` dans `SomethingOrNothing<T>`.
Ce type a un paramètre générique `T` et dérive le trait `Debug` qui permet
de faire un affichage détaillé (mais pas très joli) du contenu du type.
Ce pattern est nécessaire pour [implémenter un trait externe sur un type extern](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) (ce qui est interdit en Rust). Ainsi, il ne nous aurait pas été possible d'implémenter
le trait `Display` (qui permet d'afficher une instance du type), `Default` (qui
permet de créer une instance par défaut), ou `PartialEq` (qui permet de vérifier
l'égalité de deux instance du type) directement pour le type `Option<T>`, car
`Display`, `Default`, et `PartialEq` sont des traits externes tout comme le type
`Option<T>`. Pour des types externes l'implémentation de traits externes
est interdite (c'est la *orphan rule*). Cela interdit de "casser" un code externe
en autorisant de multiples implémentations du même trait pour le même type.
Ainsi `SomethingOrNothing<T>` nous permet d'implémenter ces trois traits
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_display}}
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_default}}
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_partialeq}}
```
Nous aimerions attirer votre attention sur une particularité du *pattern matching*
ici. On voit que nous pouvons faire un match sur des types *imbriqués*
comme pour `SomethingOrNothing(Some(val))`, on va déstructurer les types énumérés
jusqu'à obtenir `val` qui est la valeur qui nous intéresse.
Une deuxième utilité du pattern NewType est qu'elle permet de limiter
les fonctionnalités d'un type car cela nécessite de réimplémenter
les méthodes qui lui sont propres. Dans notre cas, seule la méthode `unwrap()`
de `Option<T>` nous intéresse (cette fonction retourne la valeur encapsulée dans
la variante `Some()` ou panique si on a un `None`). Ainsi, on n'implémente
que celle-là
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:newtype_unwrap}}
```
On peut noter qu'on a à faire à une `struct` avec des membres anonymes.
Ainsi, les membres peuvent être accédés comme pour les tuples et comme il n'y
en a qu'un dans un `SomethingOrNothing<T>`, on y accède avec le sélecteur `self.0`.
### Les lifetimes
Dans cette section nous discutons l'utilisation de l'annotation des lifetimes
dans différents cas: les structures, les méthodes, les traits, et les fonctions.
#### Les structures
Dans notre programme, nous utiliserons le type `CustomInt` qui est une représentation d'un entier
qui peut avoir une taille arbitraire (dans les limites de la mémoire de la machine).
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:custom_int}}
```
Un tel entier est représenté par un signe `i8` (qui peut valoir `+1` ou `-1`), et un
`Vec<u8>`, un tableau dynamique de `u8` (les valeurs admissibles vont de `0` à `9`),
qui contient les chiffres du nombre stockés de droite à gauche: `[7,3,3,1]`
est le nombre `1337`.
Ces nombres pouvant être gigantesques, nous voulons éviter de les dupliquer lorsque nous
les copions ou les manipulons. Une solution est de stocker uniquement
une référence vers les données, c'est-à-dire que `data` est de type `&Vec<u8>`.
On voit dans le code ci-dessus, qu'il est nécessaire d'annoter la durée de vie
de la référence avec `'a`. Si on omet l'annotation de durée de vie le
compilateur nous préviendra et nous oblige à en spécifier un
```console
error[E0106]: missing lifetime specifier
--> src/custom_int.rs:13:11
|
13 | data: &Vec<u8>,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
9 ~ pub struct CustomInt<'a> {
10 | /// The data contains the unsigned integers that are read from right to left
11 | /// The number 1337 is stored as vec![7, 3, 3, 1]. Each number must be in the range [0,9]
12 | /// and no trailing 0s are allowed.
13 ~ data: &'a Vec<u8>,
```
Il est en effet impossible pour le compilateur de savoir si la référence vivra
assez longtemps pour vivre plus longtemps qu'une instance de `CustomInt`.
#### Les méthodes
L'implémentation des méthodes requiert une annotation sous peine d'erreurs
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:custom_int_impl}}
```
Il en va de même avec la fonction associée, `try_new()`
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:try_new}}
```
qui nécessite
une annotation dans la définition du type de `data`. En effet, l'annotation
permet de dire au compilateur que `data`, l'argument de `try_new()`, vit suffisamment
longtemps pour permettre la création d'une instance de `CustomInt`.
#### Les traits
Il y a plusieurs traits qui sont implémentés pour `CustomInt<'a>`.: `PartialEq`, `Display`, et `Minumum`. Pour `PartialEq` et `Display`, il suffit de renseigner
l'annotation `'a` à l'instruction `impl` comme pour un paramètre générique, voir
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:partialeq}}
{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:display}}
```
Il est possible d'écrire la même chose, en omettant l'annotation `'a`
et en la remplaçant par `'_` pour simplifier la notation
```rust,ignore
impl PartialEq for CustomInt<'_>
```
Comme l'annotation n'est utilisée nulle par, Rust offre ce sucre syntaxique
pour éviter d'écrire trop d'annotations.
Pour le trait `Minimum` les choses se compliquent un peu.
Pour éviter le copies/clones, on a fait le choix de
n'utiliser que des références dans les arguments, comme dans le type de retour
du calcul du minimum de deux valeurs
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/minimum.rs:minimum}}
```
Ainsi, comme il y deux références en argument et une référence en sortie,
il est nécessaire d'annoter les références, car sinon le compilateur ne sait pas
avec que durée de vie annoter la sortie (les 2 sont possibles). Ainsi nous annotons
le trait avec la durée de vie `'a`, puis cette durée de vie est utilisée pour toutes
les références dans la fonction `min()`. Ainsi toutes les références ont la même
durée de vie que celle annotée dans le trait.
Il y a trois implémentation de ce trait: la première est pour les `i32`, la seconde pour `SomthingOrNothing<T>`, et finalement pour `CustomInt`.
1. Pour l'implémentation
pour les `i32`
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/minimum.rs:min}}
```
l'implémentation est triviale, il y a uniquement besoin de reprendre
l'annotation pour l'implémentation dans les références en argument
de la fonction et dans le retour de la fonction. En effet, comme les
deux arguments peuvent être retournés, il est nécessaire de préciser
au compilateur que la durée de vie sera la même, sinon il met automatiquement
une durée de vie à chaque argument et reprend celle à `&self` comme la durée
de vie de la sortie. En d'autres termes, sans annotations, on aurait
```rust,ignore
fn min(&self, rhs: &Self) -> &Self
```
qui serait converti automatiquement par le compilateur en
```rust,ignore
fn min(&'a self, rhs: &'b Self) -> &'a Self {
if self < rhs {
self
} else {
rhs
}
}
```
et la durée de vie `'a` du retour est pas compatible avec la durée de vie
qui serait retournée au moment de retourner `rhs` (qui est `'b`). Ce qui entraînerait une erreur de compilation (on aime pas ça les erreurs nous).
2. Pour l'implémentation pour `SomethingOrNothing<T>`, nous avons un paramètre générique.
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:min}}
```
Ainsi nous constatons, que la ligne correspondant à la déclaration
de l'implémentation du trait `Minimum` nécessite la déclaration de la durée de vie `'a`, ainsi que du type générique `T`. On voit dans l'implémentation
de la fonction `min()`, que nous faisons appel à `min()` sur le type `T`,
et que donc celui-ci doit implémenter le trait `Minimum` (tout comme le trait `PartialEq`).
On doit ainsi répercuter la durée de vie sur tous les `Minimum` présents sur la ligne `impl`
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:impl_min}}
```
Nous ne discutons pas l'implémentation à proprement parler qui est assez raisonnable
pour trouver le minimum de deux valeur encapsulées dans un `NewType`.
3. Finalement, on a l'implémentation pour `CustomInt` qui n'a rien de vraiment nouveau
par rapport aux implémentation précédentes (on réutilise l'annotation `'a` dans `min()` directement), à part la complexité monumentale de la fonction (elle fait plein de lignes)
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/custom_int.rs:min}}
```
En effet, on doit faire attention au signe, à la longueur de notre `CustomInt` et
à plusieurs autres joyeusetés. Ici, on peut utiliser le trait `Ord`
(la fonction `cmp()`) pour faire les comparaisons entre le signe et les digits de
nos nombres. Le trait `Ord` représente les opérateurs `<, >, =, <=, >=`, via la fonction `cmp()` qui retourne trois types correspondants
```rust,ignore
Ordering::Less
Ordering::Greater
Ordering::Equal
```
L'utilisation
d'un type énuméré pour gérer chacun des cas peut sembler verbeux et complexe. Cependant,
il permet de garantir à la *compilation* qu'on a pas oublié de traiter un cas par accident. Et ça, ça n'a pas de prix.
#### Les fonctions
Finalement, on utilise les lifetimes dans une fonction qui permet
le calcul du minimum dans un tableau et retourne un `SomethingOrNothing<&T>` contenant une référence vers l'élément le plus petit.
Cette fonction est générique avec le paramètre `T` et prend en argument une référence qui doivent être annotée.
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:find_min}}
```
La fonction `find_min()` prend en argument un *slice* de type générique `T` qui doit implémenter `Minimum`. Comme le type de retour est `SomethingOrNothing<&T>`, donc on
encapsule une référence vers la valeur minimale, il est nécessaire d'annoter
les durées de vies car sinon elles auraient deux valeur différentes ce qui poserait problème au compilateur (car elles doivent être les mêmes).
L'implémentation de cette fonction, n'est pas très complexe, mais est très intéressante. En premier lieu, pour des question de généricité de l'implémentation
nous passons en argument un *slice* de `T`: cette façon de procéder permet
d'avoir un argument qui serait une référence vers un tableau statique ou vers un `Vec<T>` sans changer l'implémentation. De plus, nous utilisons ici un itérateur
sur le tableau est faisons un `fold()` sur cet itérateur. Le `fold()` prend en argument
un élément neutre (quel est la valeur initiale stockée dans le `fold()`). Ici c'est
```rust,ignore
SomethingOrNothing::default()
```
puis une fonction anonyme prenant deux arguments
```rust,ignore
|res, x| {}
```
où la valeur retournée par cette fonction écrase la valeur de `res` à chaque `next()`
de l'itérateur et qui doit avoir le même type que l'élément neutre, et où `x` est la valeur courante de l'itérateur. Ici, le type de `res` est `SomethingOrNothing<&T>` et
le type de `x` est `&T`. La fonction anonyme
```rust,ignore
let r = match res {
SomethingOrNothing(None) => x,
SomethingOrNothing(Some(r)) => r.min(x),
};
SomethingOrNothing::new(r)
```
calcule le minimum entre la valeur actuelle stockée dans `res` et `x` en utilisant
la fonction `min()` ce qui implique que `T` doit implémenter `Minimum`.
### En pratique
Dans la fonction `main()` de notre programme
```rust,ignore
{{#include ../../codes/rust_lang/lifetimes/src/something_or_nothing.rs:find_min}}
```
on crée un tableau de `CustomInt` qui sont créés à partir de références
sur les tableau `v1`, `v2`, etc. qui vivrons ainsi jusqu'à la fin de notre
programme et qui seront promenées sans qu'on ait besoin de les copier à aucun moment.
Les liens entre les durées de vie des références que nous nous sommes efforcés d'annoter dan tout au long ce code sont vérifiées par le compilateur qui
vérifie qu'elles sont toutes valides à la compilation.
\ No newline at end of file
[package]
name = "collections"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"
\ No newline at end of file
//! Contains functions to interact with the user, either
//! by reading inputs from the terminal, either by writing values
//! in it.
use rand::Rng;
// ANCHOR: read_command_line
/// Poorly emulates the parsing of a command line.
pub fn read_command_line(len: usize) -> Vec<i32> {
let mut rng = rand::thread_rng();
// ANCHOR: vec_new
let mut v: Vec<i32> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_for
for _i in 0..len {
// ANCHOR: vec_push
v.push(rng.gen());
// ANCHOR: vec_push
}
// ANCHOR_END: vec_for
v
}
// ANCHOR_END: read_command_line
// ANCHOR: read_command_line_str
/// Poorly emulates the parsing of a command line.
pub fn read_command_line_str() -> Result<Vec<i32>, String> {
// ANCHOR: from
let mut s = String::from("20 10 48 58 29 0 58 -10 39 5485 394");
// ANCHOR_END: from
// ANCHOR: push_str
s.push_str(" -100");
// ANCHOR_END: push_str
// ANCHOR: push_char
s.push(' ');
s.push('1');
s.push('2');
// ANCHOR_END: push_char
// ANCHOR: split
let s: Vec<&str> = s.split_ascii_whitespace().collect();
// ANCHOR_END: split
// ANCHOR: string_for
let mut v = Vec::new();
for i in 0..s.len() {
v.push(
// ANCHOR: conversion
s.get(i)
.ok_or(String::from("Unable to index"))?
.parse()
.map_err(|_| format!("Unable to parse {}", s[i]))?,
// ANCHOR_END: conversion
);
}
// ANCHOR_END: string_for
Ok(v)
}
// ANCHOR_END: read_command_line_str
/// Prints all the elements of the `tab`.
/// Tab is borrowed here
// ANCHOR: print_tab
pub fn print_tab(tab: &[i32]) {
for t in tab {
print!("{} ", t);
}
println!();
}
// ANCHOR_END: print_tab
//! This is an example of Rust crate comments (or inner comments).
//! They will be rendered in the front page of your (crate) library.
//!
//! # How to generate the documentation
//!
//! In this program we wrote an algorithm that computes the minimum of
//! a sequence of integers.
//!
//! To create the documentation run the command
//! ```bash
//! cargo doc
//! ```
//! The obtain documentation can be found in the `target/doc/collections/index.html` directory
//!
//! To view the documentation type
//! ```bash
//! cargo doc --open
//! ```
//! which will open the browser and show you the documentation.
//!
//! The documentation supports the CommonMarkdown syntax.
//!
//! Below we will use the `///` comments that will comment the code directly below.
//! We can also sue `//` but they will not be rendered.
//! All the lines written here could be enclosed in `/*! ... */` instead of being prefixed by `//!`.
//!
//! For more informations about writing documentation [follow that link](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html).
//!
//! # Tooling
//!
//! Also Rust comes with great tooling.
//! - [Clippy](https://doc.rust-lang.org/stable/clippy/): The officiel Rust linter.
//! - [Rustfmt](https://github.com/rust-lang/rustfmt): The official Rust code formatter.
pub mod io;
pub mod minimum;
pub mod something_or_nothing;
#[test]
fn test_creation() {
use something_or_nothing::SomethingOrNothing;
let n1: SomethingOrNothing<i32> = SomethingOrNothing::default();
assert!(n1 == SomethingOrNothing::Nothing);
let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1);
assert!(n2 == SomethingOrNothing::Something(1));
}
#[cfg(test)]
mod tests {
use crate::minimum::Minimum;
use crate::something_or_nothing::{find_min, SomethingOrNothing};
#[test]
#[should_panic]
fn test_failure_creation() {
let n2: SomethingOrNothing<i32> = SomethingOrNothing::Something(1);
assert!(n2 == SomethingOrNothing::Nothing);
assert!(n2 == SomethingOrNothing::Something(2));
}
#[test]
fn test_min() {
let a = [1, 5, -1, 2, 0, 10, 11, 0, 3];
let min = find_min(&a);
assert!(min == SomethingOrNothing::Something(-1));
}
#[test]
fn test_min_something_or_nothing() {
let x = SomethingOrNothing::Something(5i32);
let y = SomethingOrNothing::Something(10i32);
let z = SomethingOrNothing::Nothing;
assert!(x.min(y) == x);
assert!(y.min(x) == x);
assert!(z.min(y) == y);
assert!(y.min(z) == y);
assert!(z.min(z) == z);
}
}
use collections::io;
use collections::something_or_nothing::find_min;
fn main() -> Result<(), String> {
//ANCHOR: vec
let tab: Vec<i32> = io::read_command_line(10usize);
println!("Among the Somethings in the list:");
io::print_tab(&tab);
let min = find_min(&tab);
min.print();
//ANCHOR_END: vec
let tab = io::read_command_line_str()?;
println!("Among the Somethings in the list:");
io::print_tab(&tab);
let min = find_min(&tab);
min.print();
//ANCHOR: ref
println!("Among the Somethings in the list:");
io::print_tab(&tab[1..9]);
let min = find_min(&tab[1..9]);
min.print();
//ANCHOR_END: ref
//ANCHOR: tab
let tab = [1, 2, 3, 4, 5, 6];
println!("Among the Somethings in the list:");
io::print_tab(&tab);
let min = find_min(&tab);
min.print();
//ANCHOR_END: tab
Ok(())
}
//! Contains a generic trait implementation for computing the minimum between two
//! values. It is the equivalent of the `<` operator.
//!
//! # Examples
//!
//! For integers this would look like
//!
//! ```
//! # use collections::minimum::Minimum;
//! let one = 1;
//! let two = 2;
//! assert!(Minimum::min(one, two) == one);
//! ```
/// The [Minimum] trait computes the minimum value between two values of a type
pub trait Minimum: Copy {
fn min(self, rhs: Self) -> Self;
}
impl Minimum for i32 {
fn min(self, rhs: Self) -> Self {
if self < rhs {
self
} else {
rhs
}
}
}
#[cfg(test)]
mod tests {
use crate::minimum::Minimum;
#[test]
fn test_min_i32() {
let x = 5;
let y = 10;
assert_eq!(Minimum::min(x, y), x);
assert_eq!(Minimum::min(y, x), x);
assert_eq!(Minimum::min(x, x), x);
assert_eq!(Minimum::min(y, y), y);
}
}
//! Contains the core logic of the library, allowing to tore generic values
//! (or their absence) and manipulate them.
use crate::minimum::Minimum;
/// An generic enumerated type that has two variants.
///
/// - Nothing
/// - Something
#[derive(Clone, Copy)]
pub enum SomethingOrNothing<T> {
/// A [SomethingOrNothing::Nothing]
Nothing,
/// A [SomethingOrNothing::Something] encapsulating a T
Something(T),
}
impl<T: std::fmt::Display> SomethingOrNothing<T> {
/// A static function that prints the content of a SomethingOrNothing.
pub fn print(&self) {
match self {
SomethingOrNothing::Nothing => println!("Nothing."),
SomethingOrNothing::Something(val) => println!("Something is: {}", val),
}
}
}
/// Implementation of the [Default] trait that creates a [SomethingOrNothing]
/// that is a `Nothing` variant.
///
/// # Example
///
/// ```
/// # use collections::something_or_nothing::SomethingOrNothing;
/// # fn main() {
/// let def: SomethingOrNothing<i32> = SomethingOrNothing::default();
/// assert!(def == SomethingOrNothing::Nothing);
/// # }
/// ```
impl<T> Default for SomethingOrNothing<T> {
/// By Default a [SomethingOrNothing] is a nothing.
fn default() -> Self {
SomethingOrNothing::Nothing
}
}
/// Implementation of the [PartialEq] trait that is useful for tests.
impl<T: PartialEq> PartialEq for SomethingOrNothing<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => true,
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => {
*lhs == *rhs
}
_ => false,
}
}
}
/// Implementation of the [Minimum] trait used for comparing values
/// in this crate.
impl<T: Minimum> Minimum for SomethingOrNothing<T> {
fn min(self, rhs: Self) -> Self {
match (self, rhs) {
(SomethingOrNothing::Nothing, SomethingOrNothing::Nothing) => {
SomethingOrNothing::Nothing
}
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Something(rhs)) => {
SomethingOrNothing::Something(lhs.min(rhs))
}
(SomethingOrNothing::Nothing, SomethingOrNothing::Something(rhs)) => {
SomethingOrNothing::Something(rhs)
}
(SomethingOrNothing::Something(lhs), SomethingOrNothing::Nothing) => {
SomethingOrNothing::Something(lhs)
}
}
}
}
/// Computes the minimum of an Array of a type T which implements the [Minimum] trait.
/// Returns a [SomethingOrNothing::Something] containing the the minimum value
/// or [SomethingOrNothing::Nothing] if no minimum value was found.
///
/// # Example
///
/// ```
/// # use collections::something_or_nothing::{SomethingOrNothing, find_min};
/// # fn main() {
/// let tab = vec![10, 32, 12, 43, 52, 53, 83, 2, 9];
/// let min = find_min(&tab);
/// assert!(min == SomethingOrNothing::Something(2));
/// # }
/// ```
// ANCHOR: find_min
pub fn find_min<T: Minimum>(tab: &[T]) -> SomethingOrNothing<T> {
let mut minimum = SomethingOrNothing::Nothing;
// Here is T is Copyable. Which means that t is not moved in the loop
for t in tab {
minimum = minimum.min(SomethingOrNothing::Something(*t));
}
minimum
}
// ANCHOR_END: find_min
[package]
name = "lifetimes"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
use std::cmp::Ordering;
use crate::minimum::Minimum;
/// Larger ints based on a [Vec] of [u8] to repensent arbitrary lengthy numbers.
/// The number has a sign as well.
// ANCHOR: custom_int
#[derive(Debug)]
pub struct CustomInt<'a> {
/// The data contains the unsigned integers that are read from right to left
/// The number 1337 is stored as vec![7, 3, 3, 1]. Each number must be in the range [0,9]
/// and no trailing 0s are allowed.
data: &'a Vec<u8>,
/// Contains the sign of the number +/-1;
sign: i8,
}
// ANCHOR_END: custom_int
// ANCHOR: custom_int_impl
impl<'a> CustomInt<'a>
// ANCHOR_END: custom_int_impl
{
/// Tries to create a new [CustomInt]. If the number is valid it returns
/// an Ok(CustomInt) an Error otherwise.
///
/// # Examples
///
/// ```
/// use lifetimes::custom_int::CustomInt;
/// let v1 = vec![1, 2, 3, 4];
/// let num = CustomInt::try_new(&v1, 1);
/// assert!(num.is_ok());
/// let num = CustomInt::try_new(&v1, -1);
/// assert!(num.is_ok());
/// let num = CustomInt::try_new(&v1, 10);
/// assert!(num.is_err());
/// let num = CustomInt::try_new(&v1, -10);
/// assert!(num.is_err());
/// let v1 = vec![];
/// let num = CustomInt::try_new(&v1, -1);
/// assert!(num.is_err());
/// ```
///
// ANCHOR: try_new
pub fn try_new(data: &'a Vec<u8>, sign: i8) -> Result<Self, String> {
if data.is_empty() {
Err(String::from("Data is empty."))
} else if sign == 1 || sign == -1 {
Ok(CustomInt { data, sign })
} else {
Err(String::from("Invalid sign."))
}
}
// ANCHOR_END: try_new
}
// ANCHOR: minimum
impl<'a> Minimum<'a> for CustomInt<'a>
// ANCHOR_END: minimum
{
// ANCHOR: min
fn min(&'a self, rhs: &'a Self) -> &'a Self {
match self.sign.cmp(&rhs.sign) {
Ordering::Less => return self,
Ordering::Greater => return rhs,
Ordering::Equal => match self.data.len().cmp(&rhs.data.len()) {
Ordering::Less => {
if self.sign == 1 {
return self;
} else {
return rhs;
}
}
Ordering::Greater => {
if self.sign == 1 {
return rhs;
} else {
return self;
}
}
Ordering::Equal => {
for (l, r) in self.data.iter().rev().zip(rhs.data.iter().rev()) {
let ls = (*l as i8) * self.sign;
let rs = (*r as i8) * self.sign;
match ls.cmp(&rs) {
Ordering::Less => return self,
Ordering::Greater => return rhs,
Ordering::Equal => {}
}
}
}
},
}
self
}
// ANCHOR_END: min
}
// ANCHOR: partialeq
impl<'a> PartialEq for CustomInt<'a>
// ANCHOR_END: partialeq
{
fn eq(&self, other: &Self) -> bool {
if self.sign == other.sign && self.data.len() == other.data.len() {
self.data
.iter()
.zip(other.data.iter())
.try_fold(true, |_, (l, r)| if *l == *r { Some(true) } else { None })
.is_some()
} else {
false
}
}
}
// ANCHOR: display
impl<'a> std::fmt::Display for CustomInt<'a>
// ANCHOR_END: display
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// This could be replaced by an `?`
if self.sign == -1 {
write!(f, "-")?;
}
// This could be replaced by an `?`
let res = self
.data
.iter()
.rev()
.try_fold((), |_, t| write!(f, "{}", t));
res
}
}
#[cfg(test)]
mod tests {
use crate::custom_int::CustomInt;
use crate::minimum::Minimum;
use crate::something_or_nothing::find_min;
#[test]
fn test_creation() {
let v1 = vec![1, 2, 3, 4];
CustomInt::try_new(&v1, 1).unwrap();
CustomInt::try_new(&v1, -1).unwrap();
}
#[test]
#[should_panic]
fn test_failure_creation_sign() {
let v1 = vec![1, 2, 3, 4];
CustomInt::try_new(&v1, 10).unwrap();
}
#[test]
#[should_panic]
fn test_failure_creation_sign2() {
let v1 = vec![1, 2, 3, 4];
CustomInt::try_new(&v1, 0).unwrap();
}
#[test]
#[should_panic]
fn test_failure_creation_data() {
let v1 = vec![];
CustomInt::try_new(&v1, 1).unwrap();
}
#[test]
fn test_min() {
let mut v = Vec::new();
let v1 = vec![1, 2, 3, 4];
let v2 = vec![1, 2, 3];
let lhs = CustomInt::try_new(&v1, 1).unwrap();
let rhs = CustomInt::try_new(&v2, 1).unwrap();
assert!(rhs == *lhs.min(&rhs));
v.push(lhs);
v.push(rhs);
let lhs = CustomInt::try_new(&v1, -1).unwrap();
let rhs = CustomInt::try_new(&v2, -1).unwrap();
assert!(lhs == *lhs.min(&rhs));
v.push(lhs);
v.push(rhs);
let v1 = vec![1, 2, 3, 4];
let v2 = vec![1, 2, 5, 4];
let lhs = CustomInt::try_new(&v1, -1).unwrap();
let rhs = CustomInt::try_new(&v2, -1).unwrap();
assert!(rhs == *lhs.min(&rhs));
v.push(lhs);
v.push(rhs);
let lhs = CustomInt::try_new(&v1, 1).unwrap();
let rhs = CustomInt::try_new(&v2, 1).unwrap();
assert!(lhs == *lhs.min(&rhs));
let min = find_min(&v);
assert_eq!(*min.unwrap(), CustomInt::try_new(&v2, -1).unwrap());
}
}
use crate::custom_int::CustomInt;
/// Prints all the elements of the `tab`.
/// Tab is borrowed here
pub fn print_tab(tab: &Vec<i32>) {
for t in tab {
print!("{} ", t);
}
println!();
}
/// Prints all the elements of the `tab`.
/// Tab is borrowed here
pub fn print_tab_custom_int(tab: &Vec<CustomInt>) {
for i in tab {
println!("{i} ");
}
println!();
}
/*!
lifetimes illustrates the use of [Vec] and the Error Handling with [Option] and [Result].
It also showcases struct enums.
*/
pub mod custom_int;
pub mod io;
mod minimum;
pub mod something_or_nothing;
#[cfg(test)]
mod tests {
use crate::minimum::Minimum;
use crate::something_or_nothing::{find_min, SomethingOrNothing};
#[test]
fn test_creation() {
let n1: SomethingOrNothing<i32> = SomethingOrNothing::default();
assert!(n1 == SomethingOrNothing::default());
let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1);
assert!(n2 == SomethingOrNothing::new(1));
}
#[test]
#[should_panic]
fn test_failure_creation() {
let n2: SomethingOrNothing<i32> = SomethingOrNothing::new(1);
assert!(n2 == SomethingOrNothing::default());
assert!(n2 == SomethingOrNothing::new(2));
}
#[test]
fn test_min() {
let a = vec![1, 5, -1, 2, 0, 10, 11, 0, 3];
let min = find_min(&a);
assert!(*min.unwrap() == -1);
}
#[test]
fn test_min_empty() {
let a: Vec<i32> = vec![];
let min = find_min(&a);
assert!(min == SomethingOrNothing::default());
}
#[test]
fn test_min_i32() {
let x = 5;
let y = 10;
assert_eq!(*Minimum::min(&x, &y), x);
assert_eq!(*Minimum::min(&y, &x), x);
assert_eq!(*Minimum::min(&x, &x), x);
assert_eq!(*Minimum::min(&y, &y), y);
}
#[test]
fn test_min_something_or_nothing() {
let x = SomethingOrNothing::new(5i32);
let y = SomethingOrNothing::new(10i32);
let z = SomethingOrNothing::default();
assert!(*x.min(&y) == x);
assert!(*y.min(&x) == x);
assert!(*z.min(&y) == y);
assert!(*y.min(&z) == y);
assert!(*z.min(&z) == z);
}
}
use lifetimes::custom_int::CustomInt;
use lifetimes::io;
use lifetimes::something_or_nothing::find_min;
// ANCHOR: main
fn main() -> Result<(), String> {
let v1 = vec![1, 3, 6, 9];
let v2 = vec![2, 4, 2, 1];
let v3 = vec![7, 4, 5, 3];
let v4 = vec![4, 1, 1, 1];
let v5 = vec![2, 5, 1, 8];
let v6 = vec![5, 1, 5, 2];
let v7 = vec![7, 6, 6, 7];
let v8 = vec![8, 2, 2, 2];
let lhs = vec![
CustomInt::try_new(&v1, 1)?,
CustomInt::try_new(&v2, -1)?,
CustomInt::try_new(&v3, 1)?,
CustomInt::try_new(&v4, -1)?,
CustomInt::try_new(&v5, 1)?,
CustomInt::try_new(&v6, 1)?,
CustomInt::try_new(&v7, 1)?,
CustomInt::try_new(&v8, 1)?,
];
println!("Among the custom ints in the list:");
io::print_tab_custom_int(&lhs);
let min = find_min(&lhs);
println!("The minimum is {min}");
Ok(())
}
// ANCHOR_END: main
// ANCHOR: minimum
pub trait Minimum<'a> {
fn min(&'a self, rhs: &'a Self) -> &'a Self;
}
// ANCHOR_END: minimum
// ANCHOR: min
impl<'a> Minimum<'a> for i32 {
fn min(&'a self, rhs: &'a Self) -> &'a Self {
if self < rhs {
self
} else {
rhs
}
}
}
// ANCHOR_END: min
use std::fmt::Display;
use crate::minimum::Minimum;
/// An generic enumerated type that encapsulates and Option<T>.
// ANCHOR: newtype
#[derive(Debug)]
pub struct SomethingOrNothing<T>(Option<T>);
// ANCHOR_END: newtype
impl<T> SomethingOrNothing<T> {
pub fn new(val: T) -> Self {
SomethingOrNothing(Some(val))
}
// ANCHOR: newtype_unwrap
pub fn unwrap(self) -> T {
self.0.unwrap()
}
// ANCHOR_END: newtype_unwrap
}
// ANCHOR: newtype_display
impl<T: Display> std::fmt::Display for SomethingOrNothing<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
SomethingOrNothing(None) => write!(f, "Nothing.")?,
SomethingOrNothing(Some(val)) => write!(f, "Something is: {}", val)?,
}
Ok(())
}
}
// ANCHOR_END: newtype_display
// ANCHOR: newtype_default
impl<T> Default for SomethingOrNothing<T> {
/// By Default a [SomethingOrNothing] is a nothing.
fn default() -> Self {
SomethingOrNothing(None)
}
}
// ANCHOR_END: newtype_default
// ANCHOR: newtype_partialeq
impl<T: PartialEq> PartialEq for SomethingOrNothing<T> {
fn eq(&self, other: &Self) -> bool {
match (&self, &other) {
(SomethingOrNothing(None), SomethingOrNothing(None)) => true,
(SomethingOrNothing(Some(lhs)), SomethingOrNothing(Some(rhs))) => lhs == rhs,
_ => false,
}
}
}
// ANCHOR_END: newtype_partialeq
// ANCHOR: min
// ANCHOR: impl_min
impl<'a, T: Minimum<'a> + PartialEq> Minimum<'a> for SomethingOrNothing<T>
// ANCHOR_END: impl_min
{
fn min(&'a self, rhs: &'a Self) -> &'a Self {
match (self, rhs) {
(SomethingOrNothing(None), SomethingOrNothing(None)) => self,
(SomethingOrNothing(Some(l)), SomethingOrNothing(Some(r))) => {
if *l == *l.min(r) {
self
} else {
rhs
}
}
(SomethingOrNothing(None), SomethingOrNothing(Some(_))) => rhs,
(SomethingOrNothing(Some(_)), SomethingOrNothing(None)) => self,
}
}
}
// ANCHOR_END: min
/// Computes the minimum of an Array of a type T which implements the [Minimum] trait.
/// Returns a [Something] containing the the minimum value
/// or [Nothing] if no minimum value was found.
///
/// # Examples
///
/// ```
/// # use lifetimes::something_or_nothing::{SomethingOrNothing, find_min};
/// # fn main() {
/// let tab = vec![10, 32, 12, 43, 52, 53, 83, 2, 9];
/// let min = find_min(&tab);
/// assert!(*min.unwrap() == 2);
/// # }
/// ```
///
/// ```
/// # use lifetimes::something_or_nothing::{SomethingOrNothing, find_min};
/// # fn main() {
/// let tab: Vec<i32> = vec![];
/// let min = find_min(&tab);
/// assert!(min == SomethingOrNothing::default());
/// # }
/// ```
// ANCHOR: find_min
pub fn find_min<'a, T: Minimum<'a>>(tab: &'a [T]) -> SomethingOrNothing<&'a T> {
// A very elegant fold applied on an iterator
tab.iter().fold(SomethingOrNothing::default(), |res, x| {
let r = match res {
SomethingOrNothing(None) => x,
SomethingOrNothing(Some(r)) => r.min(x),
};
SomethingOrNothing::new(r)
})
}
// ANCHOR_END: find_min
/// Finds the minimum values contained in two slices and returns the reference
/// towards the slice that contains it.
///
/// # Examples
///
/// ```
/// # use lifetimes::something_or_nothing::{SomethingOrNothing, vec_with_min};
/// # fn main() {
/// let tab1 = vec![10, 32, 12, 43, 52, 53, 83, 2, 9];
/// let tab2 = vec![10, 32, 12, 43, -2, 53, 83, 2, 9];
///
/// let min = vec_with_min(&tab1, &tab2).unwrap();
/// assert!(min == &tab2);
/// # }
/// ```
pub fn vec_with_min<'a, T: Minimum<'a> + PartialEq>(
lhs: &'a [T],
rhs: &'a [T],
) -> SomethingOrNothing<&'a [T]> {
match (find_min(lhs), find_min(rhs)) {
(SomethingOrNothing(None), SomethingOrNothing(None)) => SomethingOrNothing::default(),
(SomethingOrNothing(None), SomethingOrNothing(Some(_))) => SomethingOrNothing::new(rhs),
(SomethingOrNothing(Some(_)), SomethingOrNothing(None)) => SomethingOrNothing::new(lhs),
(SomethingOrNothing(Some(l)), SomethingOrNothing(Some(r))) => {
if *l == *l.min(r) {
SomethingOrNothing::new(lhs)
} else {
SomethingOrNothing::new(rhs)
}
}
}
}
......@@ -20,6 +20,6 @@
- [Vecteurs](vec.md).
- [Les Strings](string.md).
<!-- - [Itérateurs](iterators.md).
- [Unsafe Rust](collections.md).
- [Lifetimes](lifetimes.md). -->
- [Unsafe Rust](collections.md). -->
- [Lifetimes](lifetimes.md).
- [Unsafe](unsafe.md).
# Lifetimes
## Ressources
* [The Book](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html)
* [The Rustonomicon](https://doc.rust-lang.org/nomicon/lifetimes.html)
## Problématique
```rust [2|3-6|7|] compile_fail
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
```
## Durée de vie: annotation
```rust [] compile_fail
fn main() {
let r: &'a i32; // --------+-- 'a
{ // |
let x: 'b i32 = 5; // -+--'b |
r = &'b x; // | |
} // -+ |
println!("r: {}", r); // --------+ // x vit plus
}
```
* Correction?
## Inférence
* Souvent la durée de vie est **inférée**,
* Comme pour les **types**, on doit préciser quand il y a un **doute**,
* Utile que pour la **compilation**: the famous *borrow checker*,
* Si vous devez annoter votre code, il se peut que vous fassiez un truc trop compliqué.
## Exemple
```rust [8-13|1,7|] compile_fail
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
} // return lifetime is... x or y?
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
```
## Correction
```rust [1,7|]
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
} // all have the same lifetime
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
```
## Annotation
* Indique au compilateur les relations de durées de vie,
* Ne permet pas d'augmenter une durée de vie,
* Le compilateur vérifie que la durée de vie est **compatible** avec la durée de vie.
```rust [] ignore
&T // référence
&'a T // référence avec durée de vie a
&'a mut T // référence mutable avec durée de vie a
```
## Dans une structure
```rust [1-3|6-8|]
struct VecView<'a> {
view: &'a [i32]
}
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let vv = VecView {
view: &v[1..4],
};
println!("view = {:?}", vv.view);
}
```
* Le compilateur vérifie que `VecView` ne vit pas plus longtemps que sa référence `view`.
## Élision: fonction
```rust
fn vecview(v: &[i32], min: usize, max: usize) -> &[i32] {
&v[min..max]
}
```
* Règle 1: Chaque référence en argument à sa durée de vie,
* Règle 2: Si unique durée de vie en argument, la sortie obtient la même
## Élision: méthode
```rust [1-3|4-9|]
struct VecView<'a> {
view: &'a [i32]
}
impl<'a> VecView<'a> {
fn show(&self, original: &[i32]) -> &[i32] {
println!("we view {:?}, from {:?}", self.view, original);
self.view
}
}
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let vv = VecView { view: &v[1..4] };
vv.show(&v);
}
```
* Règle 3: Si un argument est `&self` ou `&mut self`, la sortie a automatiquement la même durée de vie.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment