Paradigmes 2023-2024 - TP 1

Etienne Lozes

Environnement de travail

Sur votre machine perso, il est conseillé d'installer rust avec rustup et d'utiliser vscode pour éditer le code. Il y a une extension vscode appellée rust-analyzer qui est très bien.

Sur les machines du département, recopiez le script /usr/local/Rust/var-env.sh à la fin de votre .profile pour configurer cargo et rustc, puis installez l'extension rust-analyzer dans vscode.

En dépannage, si rien ne marche, vous pouvez travailler avec Rust Playground.

Exercice 1

  1. Créez un répertoire TPs-Paradigmes-2 et un sous répertoire TP1, puis créez un projet hello_world dans TP1 avec la commande cargo. Exécutez votre programme.
  2. En vous aidant du Rust book Chapitre 12.1, modifiez votre programme pour qu'il prenne en argument de ligne de commande un nom, et affiche "hello, {nom}!" plutôt que "hello, world!". Indication: vous pourrez utiliser la méthode collect des itérateurs pour obtenir un Vec contenant tous les arguments de la ligne de commande.
  3. Que se passe-t-il si vous lancez le programme sans fournir le nom en argument? Générez un message d'erreur plus explicite avec panic!.
  4. On veut maintenant ajouter une option -g à ce petit programme. Lorsque l'option est activée, le tableau des arguments de la ligne de commande est affiché sur la sortie d'erreur avec la macro dbg!. Ensuite, on le programme affiche "hello {nom}" pour chaque nom apparaissant sur la ligne de commande, cf exemples ci-dessous
cargo run
[pas d'affichage]
cargo run alice bob
bonjour, alice!
bonjour, bob!
cargo run alice -g bob
[src/main.rs:25:9] &args = [
    "target/debug/hello_world",
    "alice",
    "-g",
    "bob",
]
bonjour, alice!
bonjour, bob!

Exercice 2

  1. Écrivez une fonction: fn concat(v1: Vec<i32>, v2: Vec<i32>) -> Vec<i32> qui renvoie la concaténation de v1 et v2.
  2. Écrivez une fonction concat2 similaire à concat sauf que ses paramètres sont empruntés, et v1 est modifié en place.
  3. Pourquoi n'est-il pas possible d'écrire une fonction fn dup<T>(x: T) -> (T, T)? Pour quels types T est-ce possible?
  4. Même question pour fn return_dangling_ptr<'a, T>(x: T) -> &'a T (rappel: les paramètres de fonction sont stockés dans la pile). Écrivez la fonction return_dangling_ptr en C, et regardez ce que dit gcc.
  5. Écrivez une fonction fn push_ampersand<T>(v: &Vec<T>) -> Vec<&T>
  6. Écrivez une fonction fn concat3<T>(v1: &Vec<T>, v2: &Vec<T>) -> Vec<&T> (en ajoutant les annotations de durées de vies nécessaires).

Exercice 3

  1. Créez un nouveau projet exo3. Dans main.rs, déclarez un type Money correspondant à un enregistrement avec deux champs:
  • cents qui est un entier non signé de la taille d'un mot machine
  • currency qui est une chaîne de caractères
  1. Implémentez une méthode to_string() pour Money (indication: utilisez la macro format!, similaire à print!, cf doc complète). On aura par exemple
let m = Money { cents: 3703, currency: "$".to_string() }
let s = m.to_string() // "37,03 $"

Testez votre code en modifiant la fonction main pour qu'elle affiche la variable s définie ci-dessus.

  1. Insérez le test ci-dessous dans main.rs, et vérifiez qu'il passe. Vous pouvez soit lancer le test depuis vscode, soit en ligne de commande avec cargo test test_money_to_string (voir documentation).
#[cfg(test)]
mod tests {
    #[test]
    fn test_money_to_string() {
        let m = Money { cents: 3703, currency: "$".to_string() }
        let s = m.to_string() // "37,03 $"
        assert_eq!(s, "37,43 $");
    }
}
  1. Écrivez une fonction convert à trois arguments:
  • une valeur de type Argent
  • une monnaie représentée par une chaîne de caractères ("$", "€", "£", "¥", etc)
  • une liste associative qui donne la valeur d'un dollars dans chacune des monnaies listées, par exemple vec![(0.93, "€"), (0.79,"£"),(150.0,"¥")] La fonction modifie son premier argument en place: elle remplace le champs currency par le second argument de la fonction, et met à jour le champs cents selon les taux de conversion passés en troisième argument. Vous utiliserez le dollars comme monnaie pivot, il faudra donc en général d'abord convertir en dollars puis dans la monnaie souhaitée.
  1. Écrivez une fonction convert_h identique à celle de la question précèdente, sauf que son troisième argument est un HashMap.

  2. Écrivez une méthode withdraw(&mut self, amount: &Argent, rates: HashMap<String,fsize>) -> Result<(),Argent> dans un bloc impl Argent. La méthode a pour effet de déduire de self.cents, lorsque c'est possible, la quantitée représentée par amount, une fois celle-ci convertie à la même unité que self; la méthode ne modifie pas le champs currency de self. La méthode utilisera la fonction convert_h de la fonction précédente sur l'argument amount, qu'il vous faudra cloner (pourquoi?). Par exemple, sans se préoccuper de la conversion de monnaie:

    • en supposant que self vaut initialement 100 € et amount vaut 10 €, la méthode met self à 90 € et termine sans erreur.
    • en supposant que self vaut initialement 100 € et amount vaut 1000 € , la méthode laisse self intact et termine avec une erreur qui contient la valeur
      1000 €.
  3. [Très facile] Écrivez une méthode repeat_withdraw(&mut self, amounts: &Vec<Argent>, rates: &HashMap<String,fsize>) -> Result<(),Argent> qui appelle withdraw successivement sur chacun des montants de la liste amounts, tant qu'il est possible de retirer. Vous utiliserez ?.