Files
BaC/content/2019-10-24_RUST-:-jouer-avec-le-typage-fort.md
2025-02-27 12:52:48 +01:00

5.6 KiB
Raw Blame History

+++

title = "Rust : jouer avec le typage fort" date = 2019-10-24 aliases = [ "post/2019/04/24/RUST-:-jouer-avec-le-typage-fort"] [taxonomies] tags = [ "codaz", "rust" ] +++ Pierre qui rouille… attendez, quoi ?!

Jai récemment appris Rust. Parce que le langage est marrant avec son concept dappartenance (ownership), sa gestion des erreurs très intelligentes, ses pointeurs intelligents (smart pointers). Parce que jaime bien apprendre des choses.

Et surtout parce que quand le shell nest plus suffisant, passer en python avait une certain tendance à me casser les noix : il faut des librairies installées ou il faut distribuer de grosses librairies avec certains programmes ou scripts alors quon peut simplement tout embarquer dans un exécutable Rust si on en a besoin.

Bref, cest cool.

Toutefois, Rust reste un langage fortement typé et quand on a lhabitude des langages faiblement typés (PHP, Python, etc…), cela peut nécessiter un sacré temps dadaptation pour sy remettre. Soyons clairs : jaime bien les langages fortement typés. Ça donne limpression de savoir ce que lon fait et ça permet déliminer un nombre substantiels de causes derreur. Mais cest aussi pas évident du tout à manipuler et ça prend nettement plus de temps pour arriver au même résultat quun langage faiblement typé.

YAY

Je nai pas la prétention de vous apprendre à programmer en Rust. Même si jai commencé à faire des choses bien plus compliquées dans ce langage, jai débuté comme tout le monde. Et au moment de débuter, on bute toujours sur des choses stupides, comme ce que je vais vous présenter là et qui a, du coup, un rapport avec une particularité du langage, le typage fort.

Si vous êtes débutant en Rust, ou si vous ne connaissez pas Rust et que vous souhaitez lapprendre, ceci pourrait vous intéresser.

Alors la marmotte, elle met le chocolat dans un entier 64 bits non-signé

Prenons un exemple tout con. On va faire une fonction dont le seul objectif est dafficher un nombre qui est donné en paramètre. Cette fonction pourrait sécrire comme ça :

fn print_number(a: u64) {
    println!("{}", a);
}

Tu peux alors lutiliser de manières assez simple :

fn main() {
    let a: u64 = 8;
    print_number(a);
}

Basiquement, ça va fonctionner, mais cela pose quand même un gros souci : on ne peut passer à cette fonction que des u64. Or, a dans notre exemple pourrait très bien tenir dans un u8. Mais si lon fait, ça, ça ne fonctionnera pas :

fn main() {
    let a: u8 = 8;
    print_number(a);
}

error[E0308]: mismatched types
 --> src/main.rs:9:18
  |
9 |     print_number(a);
  |                  ^
  |                  |
  |                  expected u64, found u8
  |                  help: you can convert an `u8` to `u64`: `a.into()`

Du point de vue de Rust, cest hyper cohérent : on lui dit quune fonction prend en paramètre un u64, on essaie de lui passer un u8, le compilateur gueule parce que ce nest pas normal. Ce nest pas le même type. Et même si dans labsolu un u8 rentre parfaitement dans un u64, Rust sen moque : un type est un type et un type « compatible » reste un type différent.

Alors évidemment, tu peux tenter de caster systématiquement ta variable dans un u64 (cest ce que propose le compilateur en fait). Pour cela, tu pourrais appeler la fonction de cette manière :

print_number(a as u64);

Ça règle effectivement le problème mais :

  • ce nest pas forcément très élégant
  • tu vas te retrouver à jongler avec des types alors que finalement, ça ne tintéresse pas des masses

Tu pourrais aussi imaginer de faire une fonction pour chaque type : u64, u32, u16, etc… mais franchement, ce serait hyper fastidieux.

Du coup, comment on fait ?

Tu peux aller timplémenter Martine !!

En fait, on peut très facilement rendre cette fonction beaucoup plus universelle en remplaçant le type fixe par un ''Trait''. Lidée est ici de dire : finalement vu ce que je fais dans la fonction, ce qui mintéresse, cest que ce qui est passé en paramètre puisse safficher dans un println! pas tellement le fait que ce soit un nombre.

Et du coup, on peut réécrire cette fonction de la manière suivante :

fn print_number(a: impl std::fmt::Display) {
    println!("{}", a);
}

À partir de ce moment, quelque soit ce quon passe dans la fonction (et même pas forcément un nombre !), tant que ce type implémente la fonction fmt::Display, ça fonctionnera !

Du coup, tu peux très bien imaginer de faire nawak :

fn main() {
    let a: u8 = 8;
    let b: u16 = 16;
    let c: f64 = 3.1416;
    
    print_number(a);
    print_number(b);
    print_number(c);
}

Conclusage

Je voulais juste donner un tout petit exemple sur une problématique qui paraît hyper simple dans un langage faiblement typé, mais qui se révèle bien plus compliqué dès que le langage est fortement typé. Quand on commence à apprendre Rust, cest typiquement le genre de choses sur lequel on bute bêtement au départ parce que ce nest pas vraiment intuitif (en tout cas, pas tant quon a pas lu le chapitre sur le sujet dans le Book).

Quand on vient du C ou du PHP, cest le type de problématique qui paraît insurmontable au départ alors quen réalité, cest tout bête à résoudre. Et je trouve personnellement que Rust a une méthode particulièrement élégante pour le résoudre en prime et évidemment, on peut extrapoler cette méthode pour se donner bien plus de possibilités.