diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e126300dc5192775b9516ea65e2e307b2b68daf3..2cb69b4ab02eec99607328a0280f843fd4ca4f95 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,14 @@ # https://hub.docker.com/r/library/rust/tags/ image: "rust:1.70-alpine3.17" # Use cargo to test the project +before_script: + - apk add --no-cache musl-dev curl + - mkdir -p $HOME/.cargo/bin + - which rustc + - which cargo + - curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.31/mdbook-v0.4.31-x86_64-unknown-linux-musl.tar.gz | tar -xz --directory=$HOME/.cargo/bin + - export PATH=$PATH:$HOME/.cargo/bin + test:cargo: script: - rustc --version && cargo --version # Print version info for debugging @@ -10,3 +18,5 @@ run_test_doc: script: - cd codes - ./run_tests.sh + - cd ../book + - mdbook build diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7585238efedfc33acdd9494b0269951aaf3909ec --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000000000000000000000000000000000000..9a31d1e76990f25a14ecdbc82df88213983dd9dd --- /dev/null +++ b/book/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Orestis Malaspinas, Michaël El Kharroubi"] +language = "fr" +multilingual = false +src = "src" +title = "Rust-101: Université d'été" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000000000000000000000000000000000000..99b1e890d5924a70d36a9347c3ed2e9dd709433d --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Part 00](./part00.md) diff --git a/book/src/part00.md b/book/src/part00.md new file mode 100644 index 0000000000000000000000000000000000000000..8d091e451deff80d3f744127d0d7235a14059d9e --- /dev/null +++ b/book/src/part00.md @@ -0,0 +1,160 @@ +# Discussion du code `part00` + +## Préambule + +Ce court texte n'a pas vocation à remplacer un cours complet, mais à rappeler les concepts importants vus dans chaque partie du cours. + +Les codes discutés ont tous pour but de calculer la valeur minimale d'entiers contenus dans un tableau. La difficulté +et l'élégance de ces codes ira en augmentant pour illustrer de façon itératives les différents concepts du présents +dans le langage. + +### Installation du compilateur Rust + +Afin d'installer le compilateur Rust, il n'est pas recommandé d'utiliser votre gestionnaire de paquet, +mais plutôt de télécharger toute la chaîne de compilation grâce à l'outil [rustup](https://www.rust-lang.org/tools/install). +Ou alors d'exécuter la commande suivante dans un terminal +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` +Ce script installera pour vous le gestionnaire de paquet et de compilation `cargo`, +le compilateur `rustc`, ainsi que le linter `clippy` et l'outil de formatage de code +`rustfmt`. + +Vous pouvez maintenant créer un nouveau projet rust (astucieusement nommé `new_project`) avec la commande +```bash +cargo new new_project +``` +Cette commande crée un répertoire `new_project`, ainsi que les fichiers `new_project/Cargo.toml` et `new_project/main.rs`. + +Vous pouvez compiler votre programme avec +```bash +cargo build +``` +Puis l'exécuter à l'aide de la commande +```bash +cargo run +``` + +La commande cargo run dépend de l'étape de compilation, par conséquent si le code n'est pas compilé, alors la commande `cargo run` lancera la compilation avant d'exécuter votre programme. + +Il est également possible de nettoyer les artéfacts de compilation ainsi que l'exécutable à l'aide de la commande + +```bash +cargo clean +``` + +## Concepts + +Les concepts abordés dans cet exemple sont: + +1. [Les variables mutables ou non, les constantes.](#variables-variables-mutables-et-constantes) +2. [Les structures de contrôle `if ... else` et `for`.](#structures-de-contrôle) +3. [L'utilisation de tableaux statiques.](#structures-de-contrôle) +4. [L'utilisation de macros pour la gestion d'erreurs ou les sorties dans le terminal.](#macros) + +## Discussion + +Chaque code Rust a un unique point d'entrée: la fonction `fn main() {}`. +Ainsi, le code le plus simple (qui ne fait absolument rien) est. +```rust,no_run +fn main() { +} +``` +Le corps de votre programme se trouvera donc entre les accolades. + +### Variables, variables mutables, et constantes + +Dans l'ordre d'apparition, nous avons d'abord une **constante** nommée SIZE, dont le type est `usize` (entier non signé dont la taille dépend de l'architecture, 8 octets sur une architecture 64 bits) et qui vaut `9`. Le nom du type vient après `:`. +```rust,no_run +const SIZE: usize = 9; +``` +Ensuite nous déclarons un tableau statique (alloué sur la pile) d'entiers 32 bits et de taille `SIZE`. +```rust,no_run +# const SIZE: usize = 9; +let tab: [i32; SIZE] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +``` +Vous notez le mot clé `let` qui permet de déclarer une variable **immutables** (les valeurs contenues dans `tab` ou sa taille ne pourront plus changer). On dit qu'on **lie** (ou **bind** en anglais) `tab` à la valeur du `[10, 32, 12, 43, 52, 53, 83, 2, 9]`. Plus bas nous déclarons au contraire une variable **mutable** (qui elle pourra changer de valeur au cours de l'exécution du programme). +```rust +# const SIZE: usize = 9; +# let tab: [i32; SIZE] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +let mut min = tab[0]; +``` +et l'initialisons avec la valeur du 1er élément de `tab` (ici la valeur est `10`). + +### Structures de contrôle + +Nous avons deux `if` dans ce code. Dans le premier +```rust,should_panic +const SIZE: usize = 0; +if SIZE == 0 { + panic!("Size is of tab = 0."); +} +``` +nous testons si `SIZE == 0` et utilisons la macro `panic!` qui lorsqu'elle est exécutée fait quitter le programme et +affiche le message d'erreur en argument. Ainsi le code ci-dessus retourne: +```bash + Compiling playground v0.0.1 (/playground) + Finished dev [unoptimized + debuginfo] target(s) in 0.56s + Running `target/debug/playground` +thread 'main' panicked at 'Size is of tab = 0.', src/main.rs:5:5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +Dans le second cas, nous sommes dans une boucle `for` +```rust,no_run +# const SIZE: usize = 9; +# let tab: [i32; SIZE] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +let mut min = tab[0]; +for i in 1..SIZE { + if min > tab[i] { + min = tab[i]; + } +} +``` +où l'indice `i` prend successivement les valeur `1` à `SIZE-1` (la notation `a..b` veut dire de `a` à `b` non inclus) et assignons +la valeur `tab[i]` à la *variable mutable* `min`. Si nous avions omis le mot clé `mut` lors de la déclaration de `min` l'assignation +donnerait une erreur (cliquez sur "play" pour la démonstration) +```rust,compile_fail +# const SIZE: usize = 9; +# let tab: [i32; SIZE] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +let min = tab[0]; +for i in 1..SIZE { + if min > tab[i] { + min = tab[i]; + } +} +``` + + +### Macros + +Outre la macro `panic!()` nous utilisons ici deux macros permettant d'afficher des chaînes de caractère +dans le terminal. Les macros sont toujours identifiées à l'aide du `!` se trouvant à la fin de l'appel, +comme pour `panic!()`, `print!()` (affiche la chaîne de caractère en argument) ou `println!()` (qui est comme `print!()` mais +retourne à la ligne après avoir affiché). Comme on le voit dans les lignes suivantes +```rust +# const SIZE: usize = 9; +# let tab: [i32; SIZE] = [10, 32, 12, 43, 52, 53, 83, 2, 9]; +# let mut min = tab[0]; +# for i in 1..SIZE { +# if min > tab[i] { +# min = tab[i]; +# } +# } +println!("Among the numbers in the list:"); +for i in 0..SIZE { + print!("{} ", tab[i]); +} +println!(); +``` +le formatage de la ligne de caractère se fait à l'aide des accolades `{}` et les macros `print!()` / `println!()` prennent un nombre +d'arguments variables. A chaque `{}` doit correspondre une variable dont on veut afficher le contenu. + +Il est également possible de numéroter chaque `{}`. Par exemple +```rust +println!("{1} {0}", "abc", "def"); +``` + +Affichera `def abc`. + + diff --git a/codes/rust_lang/part05/src/main.rs b/codes/rust_lang/part05/src/main.rs index a135c6aad37e6df02c942cafdf3ba33da2cb54df..936bf3c28f8e4386d0aff407f4d8d6d4f330f27e 100644 --- a/codes/rust_lang/part05/src/main.rs +++ b/codes/rust_lang/part05/src/main.rs @@ -123,7 +123,8 @@ fn read_command_line() -> [i32; SIZE] { [10, 32, 12, 43, 52, 53, 83, 2, 9] } -/// Prints all the elements of the `tab` and returns `tab`. +/// Prints all the elements of the `tab`. +/// Tab is borrowed here fn print_tab(tab: &[i32; SIZE]) { for t in tab { print!("{} ", t); @@ -132,7 +133,7 @@ fn print_tab(tab: &[i32; SIZE]) { } /// Computes the minimum of an Array of a type T which implements the [Minimum] trait. -/// Returns the array and 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. /// /// # Example @@ -145,20 +146,18 @@ fn print_tab(tab: &[i32; SIZE]) { /// # } /// ``` fn find_min<T: Minimum>(tab: &[T; SIZE]) -> SomethingOrNothing<T> { - let mut min = SomethingOrNothing::Nothing; - // Here is T is not Copyable tab is consumed and cannot be returned + let mut minimum = SomethingOrNothing::Nothing; + // Here is T is Copyable. Which means that t is not moved in the loop for t in tab { - min = min.min(SomethingOrNothing::Something(*t)); + minimum = minimum.min(SomethingOrNothing::Something(*t)); } - min + minimum } fn main() { let tab = read_command_line(); - println!("Among the Somethings in the list:"); print_tab(&tab); - // There are alternatives to access fields of tuples let min = find_min(&tab); // The first field is not used therefore we can replace it with "_" diff --git a/codes/rust_lang/part08/src/big_int.rs b/codes/rust_lang/part08/src/big_int.rs new file mode 100644 index 0000000000000000000000000000000000000000..d52815b485df22c780b8d2363d7b32da3384521f --- /dev/null +++ b/codes/rust_lang/part08/src/big_int.rs @@ -0,0 +1,117 @@ +use crate::minimum::Minimum; + +/// Larger ints based on a [Vec] of [u8] to repensent arbitrary lengthy numbers. +/// The number has a sign as well. +pub struct BigInt { + /// 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: Vec<u8>, + /// Contains the sign of the number +/-1; + sign: i8, +} + +impl BigInt { + /// Tries to create a new [BigInt]. If the number is valid it returns + /// an Ok(BigInt) an Error otherwise. + /// + /// # Examples + /// + /// ``` + /// use part08::big_int::BigInt; + /// let num = BigInt::try_new(vec![1, 2, 3, 4], 1); + /// assert!(num.is_ok()); + /// let num = BigInt::try_new(vec![1, 2, 3, 4], -1); + /// assert!(num.is_ok()); + /// let num = BigInt::try_new(vec![1, 2, 3, 4], 10); + /// assert!(num.is_err()); + /// let num = BigInt::try_new(vec![1, 2, 3, 4], -10); + /// assert!(num.is_err()); + /// ``` + /// + pub fn try_new(data: Vec<u8>, sign: i8) -> Result<Self, String> { + // EXERCISE: + // We don't check for trailing 0s but maybe this could be an exercise. + // Also we should check numbers are between 0 and 9. + if sign == 1 || sign == -1 { + Ok(BigInt { data, sign }) + } else { + Err(String::from("Invalid sign.")) + } + } +} + +impl Clone for BigInt { + fn clone(&self) -> Self { + BigInt { + data: self.data.clone(), + sign: self.sign, + } + } +} + +impl Minimum for BigInt { + // EXERCISE: Correct this function by using clippy and let it guide you. + // Get inspiration from Display to compute the Minimum + fn min(self, rhs: Self) -> Self { + if self.sign < rhs.sign { + return self; + } else if self.sign > rhs.sign { + return rhs; + } + if self.data.len() < rhs.data.len() { + return self; + } else if self.data.len() > rhs.data.len() { + return rhs; + } + 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; + if ls < rs { + return self; + } else if ls > rs { + return rhs; + } + } + self + } +} + +impl PartialEq for BigInt { + 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 + } + } +} + +impl std::fmt::Display for BigInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // This could be replaced by an `?` + if self.sign == -1 { + if let Err(e) = write!(f, "-") { + return Err(e); + } + } + + // This could be replaced by an `?` + let res = self + .data + .iter() + .rev() + .try_fold((), |_, t| write!(f, "{}", t)); + res + } +} + +// EXERCISE: write tests +// EXERCISE: Modify Minimum trait to take references? + +#[cfg(test)] +mod tests {}