diff --git a/slides/build_slides.sh b/slides/build_slides.sh index c6e3356008874027fbbbe9f2162447b867453953..6a708ff9c7391160fcba7e0924bcb426e0498865 100755 --- a/slides/build_slides.sh +++ b/slides/build_slides.sh @@ -6,12 +6,12 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # Build the book first, because mdbook will create any empty sections echo "Building book" -RUST_LOG=info mdbook build ${SCRIPT_DIR} +RUST_LOG=info mdbook build "${SCRIPT_DIR}" # Then build the slides echo "Building slides" -RUST_LOG=debug mdslides --template ${SCRIPT_DIR}/template.html --output-dir ${SCRIPT_DIR}/slides --mdbook-path ${SCRIPT_DIR} --index-template ${SCRIPT_DIR}/index-template.html +RUST_LOG=debug mdslides --template "${SCRIPT_DIR}/template.html" --output-dir "${SCRIPT_DIR}/slides" --mdbook-path "${SCRIPT_DIR}" --index-template "${SCRIPT_DIR}/index-template.html" # TODO: move assets copying to mdslides cp -r "${SCRIPT_DIR}/book/figs" "${SCRIPT_DIR}/slides" # Then run the tests (which is slow) echo "Testing book" -RUST_LOG=info mdbook test ${SCRIPT_DIR} \ No newline at end of file +RUST_LOG=info mdbook test "${SCRIPT_DIR}" \ No newline at end of file diff --git a/slides/src/SUMMARY.md b/slides/src/SUMMARY.md index 2303dbe4a6ea1b89a2a9d23de05e80dbb31ca2f3..90da9f734a904dafe45661ccde7ca0c2f6db0346 100644 --- a/slides/src/SUMMARY.md +++ b/slides/src/SUMMARY.md @@ -17,7 +17,9 @@ - [Traits](traits.md). - [Tests](tests.md). - [Vecteurs](vec.md). +- [Les Strings](string.md). <!-- - [Itérateurs](iterators.md). - [Unsafe Rust](collections.md). - [Lifetimes](lifetimes.md). --> +- [Unsafe](unsafe.md). diff --git a/slides/src/control.md b/slides/src/control.md new file mode 100644 index 0000000000000000000000000000000000000000..a284a42955cd149a49b9e53ad3455176cc421f9b --- /dev/null +++ b/slides/src/control.md @@ -0,0 +1,98 @@ +# Les structures de contrôle + +## Les branchements conditionnels + +```rust [3-5|6-8|9-11|] +fn main() { + let x = 15; + if x < 10 { + println!("{} est plus petit que 10.", x); + } + else if x >= 10 && x < 20 { + println!("{x} est plus grand ou égal à 10 et plus petit que 20."); + } + else { + println!("{x} est plus grand ou égal à 20."); + } +} +``` + +## C'est des **expressions** + +```rust [2|4-10|4,6,9,10|] +fn main() { + let x = -1; + let sign = + if x > 0 { + 1 // pas de ; + } else if x < 0 { + -1 // pas de ; + } else { + 0 // pas de ; + }; // attention au ; +} +``` + +## La boucle infinie: `loop` + +```rust [] ignore +fn main() { + let mut x = 0; + loop { + println!("En boucle!"); + x += 1; + } +} +``` + +## Sortie avec `break` + +```rust [3,10|8|] +fn main() { + let mut i = 0; + let j = loop { + println!("{}, en boucle!", i); + i += 1; + if i == 10 { + println!("Fin de la boucle!"); + break i; // i est optionnel + } + }; // attention au ; +} +``` + +## La boucle `while` + +```rust [3,6|] +fn main() { + let mut i = 0; + while i != 10 { + println!("{}-ème boucle!", i); + i += 1; + } + println!("Fin de la boucle!"); +} +``` + +## La boucle `for` et `continue` + +```rust [2,7|3-5|] +fn main() { + for i in 0..10 { + if i % 2 == 0 { + continue; + } + println!("{}-ème boucle!", i); + } + println!("Fin de la boucle!"); +} +``` + +## La boucle `for` ++ + +```rust ignore +for i in collection { + // instructions +} +``` + diff --git a/slides/src/figs/ferris.png b/slides/src/figs/ferris.png new file mode 100644 index 0000000000000000000000000000000000000000..43670489a4eec797ed4f239223da47c550156679 Binary files /dev/null and b/slides/src/figs/ferris.png differ diff --git a/slides/src/figs/mem_cp.png b/slides/src/figs/mem_cp.png new file mode 100644 index 0000000000000000000000000000000000000000..e1640ca99a366cdf7ae69292bca37cfff7a31875 Binary files /dev/null and b/slides/src/figs/mem_cp.png differ diff --git a/slides/src/figs/mem_mv.png b/slides/src/figs/mem_mv.png new file mode 100644 index 0000000000000000000000000000000000000000..23450801090a77dd13d4a0cc212a06d778ddcc1b Binary files /dev/null and b/slides/src/figs/mem_mv.png differ diff --git a/slides/src/figs/mem_ref_vec.png b/slides/src/figs/mem_ref_vec.png new file mode 100644 index 0000000000000000000000000000000000000000..02e14a6f185a0d867058de549d582f7cf5f7e6a2 Binary files /dev/null and b/slides/src/figs/mem_ref_vec.png differ diff --git a/slides/src/figs/mem_vec.png b/slides/src/figs/mem_vec.png new file mode 100644 index 0000000000000000000000000000000000000000..94c932d166e097a6f6e74d04efb1747f572fbb6a Binary files /dev/null and b/slides/src/figs/mem_vec.png differ diff --git a/slides/src/introduction.md b/slides/src/introduction.md index b34667caade3ca50432769f8dcb0ebd4f012edbf..5ed4f86b362b3515c84aeab435c41053ebe4b32a 100644 --- a/slides/src/introduction.md +++ b/slides/src/introduction.md @@ -36,7 +36,7 @@ - Une mascotte super mignonne - + ## Une brève histoire du Rust diff --git a/slides/src/ownership.md b/slides/src/ownership.md index 7517cef3d983838ac63be3c9e06302e223b9b876..97993087f07e278267b9d7fa3af48ccaf8650869 100644 --- a/slides/src/ownership.md +++ b/slides/src/ownership.md @@ -46,20 +46,26 @@ fn main() { } // x sort de la portée et sa valeur est détruite ``` -## Allocation de la mémoire (1/3) - -- Façons principales d'allouer/désallouer dynamiquement de la mémoire. - 1. Manuellement (C/C++, ...): on ordonne à l'OS d'allouer/désallouer de la mémoire sur le tas. - a. Oublier de désallouer la mémoire allouée (fuite mémoire/memory leak). - b. Désallouer de la mémoire trop tôt (dangling pointer et comportement indéfini). - c. Libérer la mémoire à double. - 2. Automatiquement: on a un "garbage collector" (java, scala, ...). - a. Consomme des ressources. - b. Est une "boîte magique": il fait ce qu'il veut. +## Allocation de la mémoire: Manuelle + +* Manuellement (C/C++, ...): on ordonne à l'OS d'allouer/désallouer de la mémoire sur le tas. + - Oublier de désallouer la mémoire allouée (fuite mémoire/memory leak). + - Désallouer de la mémoire trop tôt (dangling pointer et comportement indéfini). + - Libérer la mémoire à double. + +## Allocation de la mémoire: Garbage collection + +* Automatiquement: on a un "garbage collector" (java, scala, ...). + - Consomme des ressources. + - Est une "boîte magique": il fait ce qu'il veut. + +## Allocation de la mémoire: Rust + - En Rust on contrôle où et quand on alloue/désalloue à la compilation: - Difficulté: il faut suivre des règles **très** strictes. + - Garbage collection "à la compilation". -## Allocation de mémoire (2/3) +## Allocation de mémoire (tas) - Les types vus jusque là sont stockés dans la pile: leur *taille est connue* à la compilation. - Que se passe-t-il lorsque la taille est *inconnue à la compilation*? @@ -76,9 +82,9 @@ fn main() { } // x/y sortent de la portée, il sont détruits et la mémoire est libérée ``` -## Allocation de mémoire (3/3) +## Allocation de mémoire (Vec) - + - Pile: 1 pointeur vers le tas, et 2 entiers (longueur et capacité). - Tas: 1, 2, 3, 4. @@ -130,7 +136,7 @@ fn main() { - En ne faisant rien on a **deux** propriétaires des données. - Illégal: on invalide `y`. - + ## Exception au `move`: `Copy` @@ -150,7 +156,7 @@ fn main() { - *move*: copie uniquement la variable et le propriétaire **change**. - *copie*: on duplique la variable **et** les données. - + ## Quand interviennent les `move`? @@ -216,7 +222,7 @@ fn main() { ## La référence (schéma) - + ## Exemple 1 diff --git a/slides/src/smart_pointers.md b/slides/src/smart_pointers.md index d12ad321ce900f5201ac0e687ed5b9993479511c..5f5dbcf559a312c074c721dacd8be714a0a03a0b 100644 --- a/slides/src/smart_pointers.md +++ b/slides/src/smart_pointers.md @@ -5,28 +5,33 @@ - Un **pointeur** est une variable qui contient une adresse mémoire. - Cette adresse **pointe** vers des données. - + - Question: Quel type de pointeur avons-nous déjà rencontré? - Réponse: La référence. ## Smart pointers -- Un **pointeur intelligent** est un type abstrait qui rajoute des fonctionnalités au poiteur standard. +- Type abstrait qui rajoute des fonctionnalités au poiteur standard. - Management automatique de la mémoire. - Vérification de limites. -- En particulier, ils permettent la désallocation de la mémoire de manière automatique: - - On peut avoir plusieurs pointeurs sur un espace mémoire. - - Quand le dernier pointeurs est détruit, l'espace mémoire est désalloué. - - Permet d'empêcher les fuites mémoires. -- Il existe différents types: - - Un pointeur unique est le propriétaire de ses données (quand il est détruit les données aussi). - - On compte le nombre de références sur des données, quand ce nombre tombe à zéro on détruit tout. +- Permettent la désallocation de la mémoire de manière automatique: + - Plusieurs pointeurs sur un espace mémoire. + - Dernier pointeurs est détruit => espace mémoire est désalloué. + - Empêche les fuites mémoires. + +## Types de pointeurs + +- Différents types: + - Pointeur unique, propriétaire de ses données (quand il est détruit les données aussi). + - Comptage de références sur des données, quand ce nombre tombe à zéro on détruit tout. + - Mutabilité intérieure: les règles de Rust sont imposées à l'exécution. + - Accès atomiques: pas de lecture/écriture concurrente possible sur la valeur pointée. ## En Rust - Exemples: - - `Vec<T>`, `String`: ces types possèdent de la mémoire et la manipule eux-mêmes. + - `Vec<T>`, `String`: ces types possèdent de la mémoire et la manipulent eux-mêmes. - Les pointeurs intelligent doivent implémenter deux traits: - `Deref`: comment on déréférence le pointeur. - `Drop`: comment on détruit le pointeur. diff --git a/slides/src/string.md b/slides/src/string.md new file mode 100644 index 0000000000000000000000000000000000000000..0834ec963037292512177d991b7c1fe4dd530995 --- /dev/null +++ b/slides/src/string.md @@ -0,0 +1,96 @@ +# Les chaînes de caractères + +## Généralités + +- Deux types de chaînes de caractères: `str` et `String`: + - `str`: *string literal* (stocké explicitement dans l'exécutable). + - `String`: liste dynamique de caractères UTF-8. +- `String` est propriétaire de ses données. +- `str` ne peut être "manipulé" que via `&str` + +```rust +let literal = "Le type de literal est &str."; +``` + +## La structure `String` + +```console +ptr # pointeur sur le tas de char +len # nombre d'éléments utilisés +capacity # mémoire allouée +``` + +## Créer des `String`s + +```rust [1|2|3|] +let mut empty = String::new(); +let hello = String::from("Hello World!"); // converti depuis un &str +let other_hello = "Hello World!".to_string(); +``` + +## Modifier des `String`s + +```rust [1-2|] +let mut s = String::from("Hello"); +let s1 = " World!"; +s.push_str(&s1); // On peut aussi faire push_str(" World!") +println!("{s}"); +``` + +## Concaténer des `String`s + +```rust [1-4|] +let s1 = String::from("Hello"); +let s2 = String::from(" World!"); +let sum1 = s1 + &s2; // s1 moved +let sum2 = "Hello".to_string() + &s2; // s2 borrowed +println!("{sum1} : {sum2}"); +``` + +## Formatter des `String`s + +```rust +let s1 = String::from("Hello"); +let s2 = String::from("World"); +let formatted = format!("{s1} {s2}!"); +println!("{formatted}"); +``` + +## Indexer des `String` + +```rust compile_fail +let s = String::from("Hello"); +let t = s[0]; // fail +``` + +```rust +let hello = String::from("Καλημέρα!"); +println!("{}", hello.len()); +``` + +## Encodage + +* UTF-8 `!=` ASCII: encodage à longueur variable (1 à 4 octets). +* Impossible d'indexer en `O(1)`, car on connaît pas la valeur avant d'avoir lu 1 à 4 octets à chaque fois. + +## Mais alors comment faire? + +```rust [1-5|6|] +let hello = String::from("Καλημέρα!"); +for c in hello.chars() { + print!("{c}"); +} +println!(""); +println!("index 3: {}", hello.chars().nth(3).unwrap()); +``` + +## Le slice de `String` + +* Une tranche de `String` est une référence vers un bout de chaîne d'UTF-8. +* On peut facilement créer des tranches de `String`... invalides. + +```rust should_panic +let hello = String::from("Καλημέρα!"); +let h = &hello[0..3]; +``` + diff --git a/slides/src/traits.md b/slides/src/traits.md index 97e1f36b6d3845259fe8e8e9d3c828bfaffabd85..ebbd2436a911b7c2c53185d9f3a21b027a06d0ac 100644 --- a/slides/src/traits.md +++ b/slides/src/traits.md @@ -23,12 +23,12 @@ trait Animal { // définition d'un trait } // le nombre de méthode est arbitraire ``` -## Implémentation d'un trait pour un type (1/2) +## Implémentation d'un trait pour un type -- Tout type qui implémentera un trait devra implémenter **toutes** ses méthodes. -- Le compilateur saura que tout type implémentant un trait peut appeler ses méthodes. +- Un type qui implémente un trait doit implémenter **toutes** ses méthodes. +- Le compilateur sait qu'un type implémentant un trait peut appeler ses méthodes. -```rust +```rust [5-7|8-15|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn nom(&self) -> &String; @@ -50,9 +50,9 @@ fn main() { } ``` -## Implémentation d'un trait pour un type (2/2) +## Implémentation d'un trait pour deux types -```rust +```rust [15-17|18-25|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn nom(&self) -> &String; @@ -89,9 +89,9 @@ fn main() { ## Implémentations par défaut -- Il est pratique d'avoir des implémentations par défaut (les mêmes pour tous les types implémentant un trait). +- Pratique d'avoir des implémentations par défaut (les mêmes pour tous les types implémentant un trait). -```rust +```rust [1,3-5,6|] trait Animal { // définition d'un trait fn cri(&self) -> String; fn cri_puissant(&self) -> String { @@ -110,7 +110,6 @@ impl Animal for Cat { // le bloc où les méthodes du trait sont implémentées String::from("Miaou") } } - fn main() { let chien = Dog{}; println!("Le cri du chien est {} et son cri puissant est {}.", @@ -126,13 +125,12 @@ fn main() { - Lors de la définition d'un générique, on peut dire au compilateur si le type implémente un trait. -```rust -// Cette fonction ne peut pas foncitonner pour tous les types +```rust [1-5|7-9|10-12|] // T implémente PartialEq (les opérateurs <, >) fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } // si on peut pas comparer a et b } // cela ne peut pas compiler, d'où - // le PartialOrd + // le PartialOrd fn main() { let a = 1; let b = 7; diff --git a/slides/src/unsafe.md b/slides/src/unsafe.md new file mode 100644 index 0000000000000000000000000000000000000000..5b98b13b5873916e58469261b1c85d2f27b9d53a --- /dev/null +++ b/slides/src/unsafe.md @@ -0,0 +1,90 @@ +# Unsafe Rust + +* Compilateur **très pointilleux**: + * garanties de sécurité mémoire **très fortes**, + * décidées dès **la compilation**. +* Des fois le compilateur prend **trop de précautions**: + * Peut rejeter un code valide lors d'un doute, + * Rust `unsafe` donne des pouvoirs supplémentaires, + * Tant pis si l'utilisateur·trice les utilise mal. +* Les opérations de bas niveau sont `unsafe` par définition (p.ex. interactions avec l'OS). + +# Nouveaux pouvoirs + +* Déréférencer un pointeur brut, +* Appeler une fonction ou une méthode `unsafe`, +* Accéder à une variable statique mutable ou la modifier, +* Implémenter trait `unsafe`, +* Accéder aux champs des unions. + +# Les blocs `unsafe` + +```rust [1-3|4-6] +unsafe { + // code unsafe +}; +let bla = unsafe { + // le code peut retourner une valeur +}; +``` + +# Mais attention + +* Les garanties mémoires sur les références disparaissent en mode `unsafe` + +```rust compile_fail +let a = 12; +unsafe { + let b = &a; + let c = &mut a; +}; +``` + +# Le type pointeur brut + +```rust [1-2|3-4] +let a = 12; +let ptr_a: * const i32 = &a as * const i32; +let mut b = 24; +let mut_ptr_b: * mut i32 = &mut b as * mut i32; +``` + +# Propriétés des pointeurs bruts + +* `* const T`/`* mut T`: on ne peut/peut pas modifier la valeur pointée, +* Aucune garantie que le mémoire pointée est valide, +* Pas de libération automatique de la mémoire pointée lors de la sortie de la portée, +* Possibilité d'allouer des données sur le tas: `alloc()` `dealloc()`. + +# Déréférencement d'un pointeur brut + +```rust [1-3|4-6] +let mut a = 12; +let ptr_a = &a as * const i32; +let mut mut_ptr_a = &mut a as * mut i32; +unsafe { + *mut_ptr_a = 20; + println!("{}, {}", *ptr_a, *mut_ptr_a); +} +``` + +# Fonction `unsafe` + +```rust [1|2] +unsafe fn attention_not_safe() { + // everything in here is implicitly annotated unsafe +} +unsafe { + attention_not_safe(); +} +``` + +# Remarques + +* Garder le code `unsafe` aussi petit que possible, +* Aide à trouver où peuvent se trouver les erreurs de mémoire, +* Cacher le code `unsafe` dans une API `safe`, +* Permet de communiquer avec du code "externe" (des lib C), +* Annotation `unsafe` d'un bloc: fait tourner ce code c'est promis il marche, +* Annotation `unsafe fn`: attention cette fonction à des contraintes particulières. + diff --git a/slides/src/vec.md b/slides/src/vec.md index 04122444b8d8fd32e06168f7b2aa7617cde91314..44018d4cab366b630b1cb982defaf108d3a385cf 100644 --- a/slides/src/vec.md +++ b/slides/src/vec.md @@ -106,7 +106,7 @@ fn main() { } ``` -## Lecture d'éléments (2/N) +## Lecture d'éléments (1/3) ```rust compile_fail struct Int(i32); @@ -117,7 +117,7 @@ fn main() { } ``` -## Lecture d'éléments (3/N) +## Lecture d'éléments (2/3) ```rust fn main() { @@ -133,7 +133,7 @@ fn main() { } ``` -## Lecture d'éléments (4/N) +## Lecture d'éléments (3/3) ```rust fn main() {