Aller au contenu

Les arguments de ligne de commande en Rust

La plupart des petits projets qui valent la peine d’être construits pour s’entraîner (une liste de tâches, un compteur de mots…) ont besoin de recevoir des données depuis la ligne de commande, plutôt que de les écrire en dur dans le code source. Cette page couvre les bases.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}

Si vous compiliez ce code en un programme appelé greet et que vous lanciez :

Terminal window
./greet Alice

Vous verriez :

["greet", "Alice"]

Le premier élément est toujours le nom du programme lui-même : les arguments que vous avez réellement tapés commencent à l’indice 1.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Utilisation : greet <nom>");
return;
}
let name = &args[1];
println!("Bonjour, {name} !");
}

Lancez-le avec cargo run -- Alice et vous obtiendrez :

Bonjour, Alice !

Lancez-le sans argument et vous verrez le message d’utilisation au lieu d’un plantage : vérifier args.len() avant d’accéder à un indice évite une panic si quelqu’un oublie de passer un argument.

Comme chaque argument arrive sous forme de String, vous aurez souvent besoin de .parse() (vu sur la page Conversion de types) pour le transformer en nombre :

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Utilisation : square <nombre>");
return;
}
match args[1].parse::<i32>() {
Ok(n) => println!("{}", n * n),
Err(_) => println!("Ce n'est pas un nombre valide"),
}
}

Un flag (option) est un argument qui active quelque chose, comme --shout. Avant de recourir à une crate, il est utile de voir comment le gérer soi-même. L’astuce qui rend ce code facile à tester : placer la logique dans une fonction qui reçoit les arguments sous forme de slice, &[String]. Ainsi, main peut lui passer les vrais arguments, et vous pouvez lui passer une liste faite à la main pendant vos essais :

fn run(args: &[String]) {
let mut shout = false;
let mut names = Vec::new();
// on saute l'indice 0, le nom du programme
for arg in &args[1..] {
if arg == "--shout" {
shout = true;
} else {
names.push(arg);
}
}
if names.is_empty() {
println!("Utilisation : greet [--shout] <nom>...");
return;
}
for name in names {
let line = format!("Bonjour, {name} !");
if shout {
println!("{}", line.to_uppercase());
} else {
println!("{line}");
}
}
}
fn main() {
// Dans un vrai programme, vous écririez :
// let args: Vec<String> = std::env::args().collect();
// Ici, on construit la liste à la main pour que ça marche aussi dans le Playground.
let args = vec![
String::from("greet"),
String::from("--shout"),
String::from("Ana"),
String::from("Ben"),
];
run(&args);
}

Sortie :

BONJOUR, ANA !
BONJOUR, BEN !
▶ Essayez par vous-même

Quelques points à remarquer :

  • &args[1..] contient tous les arguments sauf le nom du programme. Le flag peut apparaître n’importe où : greet Ana --shout Ben fonctionne aussi.
  • Retirez --shout de la liste et relancez : vous obtenez les salutations normales. Retirez aussi les noms, et c’est le message d’utilisation qui s’affiche.
  • Comme run ne se soucie pas de l’origine de sa liste, passer à std::env::args().collect() ne demande de changer qu’une ligne dans main.

Cette approche suffit pour un ou deux flags. Dès que vous voulez aussi des versions courtes (-s), des valeurs après un flag (--count 3) et un message --help, la boucle écrite à la main grossit très vite. C’est le moment de passer à clap.

Accéder à la main aux indices de args devient vite ingérable dès qu’il vous faut des flags (--verbose), des valeurs optionnelles ou une aide --help utile. La solution standard est la crate clap, que l’on ajoute avec :

Terminal window
cargo add clap --features derive
use clap::Parser;
#[derive(Parser)]
struct Args {
name: String,
#[arg(short, long, default_value_t = 1)]
count: u32,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("Bonjour, {} !", args.name);
}
}

Vous obtenez ainsi l’analyse des arguments, leur validation et un message --help fonctionnel, le tout généré à partir de la définition de la struct. Vous n’avez pas besoin de clap pour des petits scripts rapides, mais pour tout outil que vous utiliserez plus d’une fois, il vous épargne beaucoup de travail manuel.

Le Playground ne peut pas passer de vrais arguments, donc les deux exercices partent d’un Vec<String> fait à la main, identique à ce que vous donnerait env::args().collect(). Sur votre machine, remplacez-le par le vrai appel et essayez cargo run -- ....

À partir de vec!["notes", "add", "acheter du lait"] (sous forme de String), affichez combien d’arguments l’utilisateur a tapés (sans compter le nom du programme), puis affichez chacun avec sa position, en commençant à 1.

Indice : le nombre d’arguments est args.len() - 1. Une boucle for i in 1..args.len() vous donne chaque indice après le nom du programme.

Voir la solution
fn main() {
// imaginons que ceci vienne de std::env::args().collect()
let args = vec![
String::from("notes"),
String::from("add"),
String::from("acheter du lait"),
];
println!("Vous avez passé {} arguments", args.len() - 1);
for i in 1..args.len() {
println!("{i} : {}", args[i]);
}
}

Sortie :

Vous avez passé 2 arguments
1 : add
2 : acheter du lait
▶ Essayez par vous-même

"acheter du lait" compte comme un seul argument : sur une vraie ligne de commande, vous le taperiez entre guillemets (cargo run -- add "acheter du lait"), et le shell le transmet comme une seule chaîne. L’indice 0 reste le nom du programme, c’est pour ça que la boucle commence à 1.

À partir de vec!["sum", "4", "10", "x", "6"], additionnez tous les arguments qui sont des nombres valides et affichez un avertissement pour ceux qui n’en sont pas. Terminez en affichant le total.

Indice : parcourez &args[1..] et utilisez match arg.parse::<i32>(), comme dans Convertir un argument en nombre. Déclarez let mut total = 0; en dehors de la boucle.

Voir la solution
fn main() {
let args = vec![
String::from("sum"),
String::from("4"),
String::from("10"),
String::from("x"),
String::from("6"),
];
let mut total = 0;
for arg in &args[1..] {
match arg.parse::<i32>() {
Ok(n) => total += n,
Err(_) => println!("On ignore '{arg}' : ce n'est pas un nombre"),
}
}
println!("Total : {total}");
}

Sortie :

On ignore 'x' : ce n'est pas un nombre
Total : 20
▶ Essayez par vous-même

Chaque argument arrive sous forme de texte, même "4", donc chacun doit être converti. Ignorer une entrée invalide avec un message est plus agréable qu’un plantage. Vous pourriez aussi vous arrêter à la première erreur avec return, si cela convient mieux à votre programme.

  • std::env::args().collect() vous donne un Vec<String> des arguments de ligne de commande ; l’indice 0 est toujours le nom du programme.
  • Vérifiez args.len() avant d’accéder à un indice, pour qu’un argument manquant ne fasse pas paniquer votre programme.
  • .parse() transforme un argument String en nombre, comme partout ailleurs.
  • Placez la gestion des arguments dans une fonction qui reçoit &[String], pour pouvoir la tester avec une liste faite à la main.
  • Au-delà de quelques arguments simples, la crate clap est le choix standard et éprouvé.

Petit quiz

1. Quand vous appelez env::args().collect(), quel est le premier élément du Vec obtenu ?

2. Pourquoi vérifier args.len() avant d'accéder à un indice de args ?

3. Qu'apporte la crate clap par rapport à l'accès manuel aux indices de args ?

Score : 0 / 3