Files
BaC/content/2022-12-09-Rust-:-passer-de-clapv2-à-clapv4.md
2025-02-27 12:52:48 +01:00

7.9 KiB
Raw Blame History

+++ title = "Rust: passer de clapv2 à clapv4" date = 2022-12-09 [taxonomies] tags = [ "rust", "clap" ] +++

Ah ouais, comme pour IPv4 et IPv6, tas zappé le 3?

Ta gueule…

Récemment, je me suis rendu compte que certains des logiciels que javais développés par le passé, en Rust donc, utilisait une «assez vieille» version de la librairie clap, la v2 pour être précis.

En fait, jai complètement zappé la v3, parce quelle me semblait à ce moment nettement moins optimale, beaucoup plus brute, imposait tout un tas de choix discutable comme le fait de passer obligatoirement par des macros plutôt que davoir un moyen «programmatique» dappeler la librairie.

De ce point de vue, la v4 correspond bien plus à ce que jattends dun programme de ce type: le choix entre macros ou pas, la versatilité des options, les cas tordus quon na pas forcément vus au départ.

Voici un (petit) guide pour aider à passer de clapv2 à clapv4.

Command::new() remplace App:new()

Voilà, la première des choses à faire, cest dinvoquer correctement clapv4. Pour cela, on utilise la fonction Command::new() que lon doit invoquer à limport de clap.

App nexiste plus du tout, même si ce nest pas la première erreur que tu vas rencontrer a priori.

Arg::with_name nexiste plus

Autre chose dont il va falloir se débarasser, Arg::with_name nexiste plus, il est remplacé par Arg::new(). On peut ainsi facilement remplacer ce bout de code:

App::new()
    .arg(Arg::with_name("host")
         .short("H")
         .long("host")
         .value_name("HOST"));

Par:

Command::new()
    .arg(Arg::new("host")
         .short("H")
         .long("host")
         .value_name("HOST"));

Plus simple, plus lisible.

takes_value() nexiste plus non plus

En fait takes_value() avait un peu un statut à la con à la lorigine: il permettait de spécifier quun argument devait prendre une valeur, mais il fallait une seconde fonction pour préciser le nombre de valeurs sil y en avait plus dune. Cest maintenant régler sur clapv4 où lon utilise systématiquement la fonction num_args(). Il suffit de linvoquer avec la valeur 1 pour retrouver le comportement dorigine et lon verra un peu plus bas que ça a des conséquences assez positives sur le reste du code.

short() prend maintenant un caractère et non une chaîne

Voilà un changement qui est le bienvenue: on ne peut plus écrire short("b"), il faut obligatoirement écrire short('b'). Et oui, en Rust, les chaînes de caractères sont entre double quotes alors que les caractères sont obligatoirement entre single quotes (un truc quon a beaucoup de mal à faire comprendre au départ à des gens qui font du PHP ou du Python dailleurs…).

Dites aussi au revoir à value_of()

Bon alors là, je ne peux quapplaudir le progrès aussi. Au lieu dutiliser value_of() et de décapsuler léventuel résultat, il va maintenant sagir dutiliser get_one() (ou son pendant get_many()) pour récupérer les valeurs que lon souhaite. La (très) bonne nouvelle, cest que get_one() permet de directement spécifier le type quon souhaite récupérer à la fin.

On peut donc parfaitement écrire:

let my_port = matches.get_one::<String>("host").unwrap_or("default");

Plutôt que:

let my_port = matches.value_of("host").unwrap().to_string();

Je trouve ça bien plus élégant dans le principe et même dans la pratique, dans la mesure où le cast est fait directement par la fonction et non en aval de celle-ci. On récupère donc bien plus directement les valeurs qui nous intéressent (et encore une fois, si vous naimez pas lopérateur turbofish, vous pouvez toujours venir me lécher le cul…).

Attention, toutefois! Vous ne récupérez pas la valeur directement mais un emprunt à cette valeur. Méfiez-vous donc pour les String par exemple. Ça veut aussi dire quil faudra de temps en temps se servir de lopérateur *, ce qui est relativement inhabituel en Rust.

Les flags doivent maintenant être utilisés systématiquement

Avant pour préciser un flag (comprendre une valeur optionnelle sur la ligne de commande), il fallait préciser un argument sans takes_value() ou avec takes_value(false). Désormais, il faut transformer cette argument en flag (un drapeau donc) avec une valeur par défaut (typiquement si faux, si vrai, etc…).

On passe donc de:

let matches = App::new()
    .arg(
        Arg::with_name("starttls")
        .short("t")
        .long("starttls"));

let starttls = matches.is_present("starttls");

À :

let matches = Command::new()
    .arg(Arg::new("starttls")
        .short('t')
        .long("starttls")
        .action(ArgAction::SetTrue));

let starttls = matches.get_flag("starttls");

Voilà, encore une fois un peu plus lourd à linstantiation, mais du coup bien plus «explicite» quand on lit juste la partie définition les arguments.

Précisez les valeurs par défaut directement dans les arguments

Toujours dans le même état desprit, vous pouvez maintenant préciser une valeur par défaut (toujours sous la forme dune chaîne de caractères) directement dans les arguments. Donc au lieu de tenter des unwrap_or() un peu au pif dans le code, on peut directement préciser une valeur dans largument pris.

Cest, encore une fois, un changement qui va dans le bon sens, dautant plus quil est repris dans laide de la commande quon tape (entre crochet à côté de la valeur). Donc une très bonne option pour rendre les choses bien plus claires.

En gros, on peut maintenant faire:

let matches = Command::New()
        .arg(Arg::new("port")
        .short('p')
        .long("port")
        .num_args(1)
        .default_value("993"));

let port = matches.get_one::<u16>("port").unwrap(); // va contenir 993 quoiquil arrive

Sous-commandes

Et pour finir dans ce petit tour de clapv4, les subcommand (qui exigeaint à lorigine leur propres structures) sont maintenant simplement des Command::new(). Il ny a donc pas besoin davoir une commande spécifique pour les sous-commandes, on peut directement faire des commandes dans des commandes, en continuant dutiliser subcommand.

Seul petit piège, subcommand (celui du matches, pas lautre) renvoie maintenant un tuple dans une Option au lieu de renvoyer une Option dans un tuple. Cest un changement assez minime dans labsolu, mais ça va forcément foutre en lair ta syntaxe dorigine, cest balot…

Conclusation

Voilà, cétait un petit retour sur le passage de v2 à v4 de clap. clap, cest vraiment la librairie un peu indispensable dès quon veut mettre des options dans un programme Rust. Essayer de sen passer, cest un peu comme tenter de faire le Tour de France avec les petites roues en ayant mangé une choucroute avant de se lancer: ya moyen que tu te gamèles au premier virage avec pertes et fracas.

clap, cest bon, mangez-en!