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

Merge branch 'part04summary' into 'main'

Adds part04 in summaries

See merge request orestis.malaspin/rust-101!26
parents 06adb4e6 81f8a311
No related branches found
No related tags found
No related merge requests found
...@@ -6,5 +6,4 @@ ...@@ -6,5 +6,4 @@
- [Part 01](./part01.md) - [Part 01](./part01.md)
- [Part 02](./part02.md) - [Part 02](./part02.md)
- [Part 03](./part03.md) - [Part 03](./part03.md)
- [Part 04](./part04.md)
# Discussion du code `part04`
Dans cette partie nous discutons de [ce code](#le-code).
## Concepts
Les concepts abordés dans cet exemple sont:
1. [Bind et move.](#bind-et-move)
2. [Ownership et borrowing.](#ownership-et-borrowing)
## Discussion
### Bind et move
On l'a déjà brièvement mentionné dans la [partie 2](part02.md) l'instruction
```rust,no_run
let a = 2;
```
fait plusieurs choses:
1. Elle alloue l'espace pour un entier 32 bits.
2. Initialise la mémoire avec la valeur `2`.
3. Lie (bind en anglais) la variable (l'identifiant) `a` avec cet espace mémoire.
Quand on sort de la portée (scope en anglais) la variable est plus accessible et la mémoire est automatiquement libérée (l'espace mémoire
où est contenue la valeur `2` est libéré automatiquement). Cela se passe aussi en C, sauf quand on a fait un `malloc()` explicite.
Si on écrit le code un peu plus compliqué
```rust,compile_fail
{{#include ../../codes/rust_lang/part04/src/main.rs:something_or_nothing}}
fn main() {
let a = SomethingOrNothing::new(1);
let _b = a;
let _c = a;
}
```
Ici ce code ne compile pas, car nous ne pouvons lier une valeur qu'à **une seule variable** par défaut.
On dit que les données ne peuvent avoir qu'un seul propriétaire (owner en anglais).
Ainsi, quand on fait
```rust,ignore
let a = SomethingOrNothing::new(1);
let _b = a;
```
on donne la propriété de la valeur `SomethingOrNothing::new(1)` à la variable `a`. Puis, quand transfère la propriété de la valeur
`SomethingOrNothing::new(1)` à la variable `_b`. Ainsi, `a` n'est plus lié à `SomethingOrNothing::new(1)` et ne peut plus rien transférer
comme données. Donc quand on fait
```rust,ignore
let _c = a;
```
le compilateur refuse de nous laisser faire, car les données liées à `a` ont déjà été déplacées (moved en anglais) dans `_b`.
Pour certains types, comme les `i32` (ou tous les types qui implémentent le trait `Copy`)
une copie implicite est faite, et on ne déplace ainsi pas la propriété de la valeur mais on en crée
une nouvelle qui est liée aux variables.
Cette règle est **très importante** et a des implications très fortes en Rust (c'est aussi le cas dans tous les langages
mais ça passe un peu plus inaperçu, car le compilateur ne se plaint pas). On dit que Rust empêche l'*aliasing*.
On retrouve cette difficulté quand on passe un argument à une fonction. Si le type de la variable passé en argument
n'est pas copiable, la propriété de la valeur est passée à l'argument de la fonction.
Ainsi au moment de quitter la fonction la valeur est détruite et la mémoire liée à la valeur libérée
comme dans le code suivant
```rust,compile_fail
{{#include ../../codes/rust_lang/part04/src/main.rs:something_or_nothing}}
fn print<T: std::fmt::Display>(val: SomethingOrNothing<T>) {
match val {
SomethingOrNothing::Nothing => println!("Nothing."),
SomethingOrNothing::Something(val) => println!("Something is: {}", val),
}
}
fn main() {
let a = SomethingOrNothing::new(1);
print(a);
print(a);
}
```
Ici lors du premier appel à `print(a)` la propriété de la valeur `SomethingOrNothing::new(1)` est transférée à l'argument `val` de la fonction
`print(val)`. Lorsqu'on sort de la portée de la fonction la variable `val` est détruite et toutes les données qui vont avec.
Comme la variable `a` n'est plus propriétaire d'aucune donnée, le second appel à `print(a)` échoue à la compilation.
Une solution est d'implémenter le trait `Copy` pour `SomethingOrNothing<T>`, mais bien que cela puisse être acceptable
pour un petit type comme celui-ci, en général le coût de performance serait énorme on devait répéter des copies
qui prennent beaucoup de place en mémoire.
Mais alors comment faire pour afficher un `SomethingOrNothing<T>` sans implémenter le trait `Copy`?
### Ownership et borrowing
Le Rust nous offre un moyen d'*emprunter* (borrow) les variables pour éviter d'avoir à faire des copies dans tous les sens
ou de devoir toujours retourner les variables passées en argument.
```rust
{{#include ../../codes/rust_lang/part04/src/main.rs:something_or_nothing}}
{{#include ../../codes/rust_lang/part04/src/main.rs:print}}
fn main() {
let a = SomethingOrNothing::new(1);
print(&a);
print(&a);
}
```
Dans ce code on voit que la fonction `print` a la signature suivante
```rust,ignore
fn print<T: std::fmt::Display>(val: &SomethingOrNothing<T>)
```
et le type de `val` est une **référence** vers un `SomethingOrNothing<T>` (il faut noter le `&` avant le type). La référence en Rust est un moyen d'emprunter
la propriété d'une valeur. Ainsi quand on va sortir de la portée de la fonction, la variable `val`, qui est une référence
va être détruite, mais les données qui sont liées à cette référence ne le seront pas. C'est pour cela que contrairement à
ce qui se passe dans la section [Bind et move](#bind-et-move) on peut appeler deux fois la fonction `print(&val)`.
Il faut bien noter que la *référence* a la syntaxe `&<variable>` comme en C.
#### Exercice
Corrigez ce code pour qu'il compile (sans enlever de lignes évidemment)
```rust,editable,compile_fail
{{#include ../../codes/rust_lang/part04/src/main.rs:something_or_nothing}}
fn main() {
let a = SomethingOrNothing::new(1);
{
let b = a;
// imagine that some thing may happen here with b
}
let c = a;
}
```
On constate dans notre code qu'il y a assez peu de modifications pour éviter les copies
implicites et passer par des emprunts. Pour résumer, on a changé la signature de trois fonctions:
`print()`, `print_tab()`, et `find_min()`.
La fonction `print()` a déjà été discutée plus haut, et nécessite simplement
l'ajout d'une référence vers un `SomethingOrNothing<T>` pour que la valeur soit
prêtée à `val` dans la fonction
```rust,ignore
{{#include ../../codes/rust_lang/part04/src/main.rs:print}}
```
Ainsi, les valeurs de `SomethingOrNothing<T>` ne sont pas détruites à l'appel de `print()`.
La fonction `print_tab()`, de façon similaire, nécessite le passage de la référence
vers le tableau statique `&[T; SIZE]`
```rust,ignore
{{#include ../../codes/rust_lang/part04/src/main.rs:print_tab}}
```
La boucle for nécessite une discussion plus approfondie. En effet,
dans cette fonction `tab` est une référence vers un tableau. Ainsi,
on ne peut pas itérer sur ses éléments de façon standard. En effet,
cela nécessiterait de "move" ou transférer la propriété de la valeur
sur laquelle on itère.
Le compilateur de Rust ne l'autoriserait jamais. Le langage nous facilite donc la vie en itérant sur
les références des valeurs contenues dans le tableau (dont le type est générique, rappelons le).
Donc si on annotait explicitement le type de `t` dans la boucle `for` on aurait:
```rust,ignore
for t: &T in tab
```
On constate aussi que le langage devine qu'on ne veut pas afficher la valeur de la référence (son adresse mémoire)
mais affiche directement la valeur elle même quand on appelle la macro `print!()`
```rust,ignore
print!("{} ", t);
```
Finalement, nous avons également modifié la fonction `find_min()`.
```rust,ignore
{{#include ../../codes/rust_lang/part04/src/main.rs:find_min}}
```
Outre l'argument qui est une référence à un tableau statique comme dans les deux autres fonctions
discutées précédemment, on a une nouvelle syntaxe dans la boucle `for`
```rust,ignore
for t in tab {
current_minimum = current_minimum.min(SomethingOrNothing::new(*t));
}
```
On constante qu'on doit déréférencer `t` (appeler `*t`), car `t` est une référence vers `T` (il est de type `&T`), car la fonction `new()` s'attend à un `T` et non à un `&T`.
L'opérateur `*<variable>` est l'opérateur de déréférencement et cette syntaxe est la même que celle du C.
Si par malheur on oubliait le `*t` on aurait une erreur de compilation, car
le vérificateur de type du Rust ne laisse pas passer ce genre de choses.
## Le code
```rust
{{#rustdoc_include ../../codes/rust_lang/part04/src/main.rs:main}}
```
/* ANCHOR: all */
/*! /*!
Part04 illustrates the concepts of **Ownership** and **Borrowing**. It also Part04 illustrates the concepts of **Ownership** and **Borrowing**. It also
presents the manual implementation of [Clone] and [Copy]. presents the manual implementation of [Clone] and [Copy].
*/ */
// ANCHOR: something_or_nothing
// Rust basics:
// - Ownership
// - Borrowing
// - Clone
// - Copy
// An generic enumerated type that has two variants.
//
// - Nothing
// - Something
enum SomethingOrNothing<T> { enum SomethingOrNothing<T> {
// A [SomethingOrNothing::Nothing]
Nothing, Nothing,
// A [SomethingOrNothing::Something] encapsulating a T
Something(T), Something(T),
} }
impl<T> SomethingOrNothing<T> {
fn new(val: T) -> SomethingOrNothing<T> {
SomethingOrNothing::Something(val)
}
}
// ANCHOR_END: something_or_nothing
impl<T: std::fmt::Display> SomethingOrNothing<T> { // ANCHOR: print
// A static function that prints the content of a SomethingOrNothing. fn print<T: std::fmt::Display>(val: &SomethingOrNothing<T>) {
fn print(&self) { match val {
match self { SomethingOrNothing::Nothing => println!("Nothing."),
SomethingOrNothing::Nothing => println!("Nothing."), SomethingOrNothing::Something(val) => println!("Something is: {}", val),
SomethingOrNothing::Something(val) => println!("Something is: {}", val),
}
} }
} }
// ANCHOR_END: print
// Manual implementation of [Clone] // Manual implementation of [Clone]
impl<T: Clone> Clone for SomethingOrNothing<T> { impl<T: Clone> Clone for SomethingOrNothing<T> {
...@@ -45,10 +40,13 @@ impl<T: Copy> Copy for SomethingOrNothing<T> {} ...@@ -45,10 +40,13 @@ impl<T: Copy> Copy for SomethingOrNothing<T> {}
// If we remove Copy, we have a problem with the t in tab // If we remove Copy, we have a problem with the t in tab
// in the computation of the minimum. // in the computation of the minimum.
// ANCHOR: minimum
trait Minimum: Copy { trait Minimum: Copy {
fn min(self, rhs: Self) -> Self; fn min(self, rhs: Self) -> Self;
} }
// ANCHOR_END: minimum
// ANCHOR: minimum_impl
impl<T: Minimum> Minimum for SomethingOrNothing<T> { impl<T: Minimum> Minimum for SomethingOrNothing<T> {
fn min(self, rhs: Self) -> Self { fn min(self, rhs: Self) -> Self {
match (self, rhs) { match (self, rhs) {
...@@ -67,6 +65,7 @@ impl<T: Minimum> Minimum for SomethingOrNothing<T> { ...@@ -67,6 +65,7 @@ impl<T: Minimum> Minimum for SomethingOrNothing<T> {
} }
} }
} }
// ANCHOR_END: minimum_impl
impl Minimum for i32 { impl Minimum for i32 {
fn min(self, rhs: Self) -> Self { fn min(self, rhs: Self) -> Self {
...@@ -87,31 +86,37 @@ fn read_command_line() -> [i32; SIZE] { ...@@ -87,31 +86,37 @@ fn read_command_line() -> [i32; SIZE] {
// Prints all the elements of the `tab`. // Prints all the elements of the `tab`.
// Tab is borrowed here // Tab is borrowed here
fn print_tab(tab: &[i32; SIZE]) { // ANCHOR: print_tab
fn print_tab<T: std::fmt::Display>(tab: &[T; SIZE]) {
for t in tab { for t in tab {
print!("{} ", t); print!("{} ", t);
} }
println!(); println!();
} }
// ANCHOR_END: print_tab
// Computes the minimum of a borrowed Array of a type T which implements the [Minimum] trait. // Computes the minimum of a borrowed Array of a type T which implements the [Minimum] trait.
// Returns a [SomethingOrNothing::Something] containing the the minimum value // Returns a [SomethingOrNothing::Something] containing the the minimum value
// or [SomethingOrNothing::Nothing] if no minimum value was found. // or [SomethingOrNothing::Nothing] if no minimum value was found.
// ANCHOR: find_min
fn find_min<T: Minimum>(tab: &[T; SIZE]) -> SomethingOrNothing<T> { fn find_min<T: Minimum>(tab: &[T; SIZE]) -> SomethingOrNothing<T> {
let mut minimum = SomethingOrNothing::Nothing; let mut current_minimum = SomethingOrNothing::Nothing;
// Here is T is Copyable. Which means that t is not moved in the loop // Here is T is not Copyable tab is consumed and cannot be returned
for t in tab { for t in tab {
minimum = minimum.min(SomethingOrNothing::Something(*t)); current_minimum = current_minimum.min(SomethingOrNothing::new(*t));
} }
minimum current_minimum
} }
// ANCHOR_END: find_min
// ANCHOR: main
fn main() { fn main() {
let tab = read_command_line(); let tab = read_command_line();
println!("Among the Somethings in the list:"); println!("Among the Somethings in the list:");
print_tab(&tab); print_tab(&tab);
// There are alternatives to access fields of tuples
let min = find_min(&tab); let min = find_min(&tab);
// The first field is not used therefore we can replace it with "_" print(&min);
min.print();
} }
// ANCHOR_END: main
/* ANCHOR_END: all */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment