+++ title = "Rust : passer de clapv2 à clapv4" date = 2022-12-09 [taxonomies] tags = [ "rust", "clap" ] +++ > − Ah ouais, comme pour IPv4 et IPv6, t’as zappé le 3 ? > > − Ta gueule… Récemment, je me suis rendu compte que certains des logiciels que j’avais développés par le passé, en Rust donc, utilisait une « assez vieille » version de la librairie [clap](https://epage.github.io/blog/), la v2 pour être précis. En fait, j’ai complètement zappé la v3, parce qu’elle 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 d’avoir un moyen « programmatique » d’appeler la librairie. De ce point de vue, la v4 correspond bien plus à ce que j’attends d’un programme de ce type : le choix entre macros ou pas, la versatilité des options, les cas tordus qu’on n’a 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, c’est d’invoquer correctement clapv4. Pour cela, on utilise la fonction [`Command::new()`](https://docs.rs/clap/4.0.29/clap/builder/struct.Command.html#method.new) que l’on doit invoquer à l’import de `clap`. `App` n’existe plus du tout, même si ce n’est pas la première erreur que tu vas rencontrer a priori. # `Arg::with_name` n’existe plus Autre chose dont il va falloir se débarasser, `Arg::with_name` n’existe plus, il est remplacé par [`Arg::new()`](https://docs.rs/clap/4.0.29/clap/builder/struct.Arg.html#method.new). On peut ainsi facilement remplacer ce bout de code : ```rust App::new() .arg(Arg::with_name("host") .short("H") .long("host") .value_name("HOST")); ``` Par : ```rust Command::new() .arg(Arg::new("host") .short("H") .long("host") .value_name("HOST")); ``` Plus simple, plus lisible. # `takes_value()` n’existe plus non plus En fait `takes_value()` avait un peu un statut à la con à la l’origine : il permettait de spécifier qu’un argument devait prendre une valeur, mais il fallait une seconde fonction pour préciser le nombre de valeurs s’il y en avait plus d’une. C’est maintenant régler sur clapv4 où l’on utilise systématiquement la fonction [`num_args()`](https://docs.rs/clap/4.0.29/clap/builder/struct.Arg.html#method.num_args). Il suffit de l’invoquer avec la valeur `1` pour retrouver le comportement d’origine et l’on 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 qu’on a beaucoup de mal à faire comprendre au départ à des gens qui font du PHP ou du Python d’ailleurs…). # Dites aussi au revoir à `value_of()` Bon alors là, je ne peux qu’applaudir le progrès aussi. Au lieu d’utiliser `value_of()` et de décapsuler l’éventuel résultat, il va maintenant s’agir d’utiliser [`get_one()`](https://docs.rs/clap/4.0.29/clap/parser/struct.ArgMatches.html#method.get_one) (ou son pendant [`get_many()`](https://docs.rs/clap/4.0.29/clap/parser/struct.ArgMatches.html#method.get_many)) pour récupérer les valeurs que l’on souhaite. La (très) bonne nouvelle, c’est que [`get_one()`](https://docs.rs/clap/4.0.29/clap/parser/struct.ArgMatches.html#method.get_one) permet de directement spécifier le type qu’on souhaite récupérer à la fin. On peut donc parfaitement écrire : ```rust let my_port = matches.get_one::("host").unwrap_or("default"); ``` Plutôt que : ```rust 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 n’aimez pas l’opé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 qu’il faudra de temps en temps se servir de l’opé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 : ```rust let matches = App::new() .arg( Arg::with_name("starttls") .short("t") .long("starttls")); let starttls = matches.is_present("starttls"); ``` À : ```rust 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 à l’instantiation, 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 d’esprit, vous pouvez maintenant préciser une valeur par défaut (toujours sous la forme d’une chaîne de caractères) directement dans les arguments. Donc au lieu de tenter des [`unwrap_or()`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or) un peu au pif dans le code, on peut directement préciser une valeur dans l’argument pris. C’est, encore une fois, un changement qui va dans le bon sens, d’autant plus qu’il est repris dans l’aide de la commande qu’on 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 : ```rust let matches = Command::New() .arg(Arg::new("port") .short('p') .long("port") .num_args(1) .default_value("993")); let port = matches.get_one::("port").unwrap(); // va contenir 993 quoiqu’il arrive ``` # Sous-commandes Et pour finir dans ce petit tour de clapv4, les `subcommand` (qui exigeaint à l’origine leur propres structures) sont maintenant simplement des [`Command::new()`](https://docs.rs/clap/4.0.29/clap/builder/struct.Command.html#method.new). Il n’y a donc pas besoin d’avoir une commande spécifique pour les sous-commandes, on peut directement faire des commandes dans des commandes, en continuant d’utiliser [`subcommand`](https://docs.rs/clap/4.0.29/clap/builder/struct.Command.html#method.subcommand). Seul petit piège, [`subcommand` (celui du matches, pas l’autre)](https://docs.rs/clap/4.0.29/clap/parser/struct.ArgMatches.html#method.subcommand) renvoie maintenant un tuple dans une Option au lieu de renvoyer une Option dans un tuple. C’est un changement assez minime dans l’absolu, mais ça va forcément foutre en l’air ta syntaxe d’origine, c’est balot… # Conclusation Voilà, c’était un petit retour sur le passage de v2 à v4 de `clap`. `clap`, c’est vraiment la librairie un peu indispensable dès qu’on veut mettre des options dans un programme Rust. Essayer de s’en passer, c’est un peu comme tenter de faire le Tour de France avec les petites roues en ayant mangé une choucroute avant de se lancer : y’a moyen que tu te gamèles au premier virage avec pertes et fracas. `clap`, c’est bon, mangez-en !