Introduction
Un programme en ligne de commande reçoit des valeurs après le nom de son exécutable, écrit les résultats normaux sur la sortie standard et signale les échecs via la sortie d’erreur standard ainsi qu’un statut de sortie différent de zéro. Ces limites entre le processus et son environnement permettent aux utilisateurs et aux scripts de distinguer une réussite d’un échec.
Vous allez terminer une petite commande de recherche de texte. Sa fonction de recherche est déjà préparée dans le code source de la bibliothèque du paquet. Chaque étape peut donc se concentrer sur l’analyse des arguments, le chargement du fichier, le comportement lorsqu’aucune correspondance n’est trouvée et la communication finale du processus.
Analyser les arguments de ligne de commande
Dans cette étape, vous allez valider et extraire une requête de recherche ainsi qu’un chemin de fichier à partir des arguments du processus.
Le projet se trouve dans /home/labex/project/mini-search. src/main.rs contient le binaire en ligne de commande, tandis que src/lib.rs contient la logique de recherche réutilisable préparée pour une étape ultérieure. Accédez au projet et ouvrez le code source du binaire :
cd /home/labex/project/mini-search
nano src/main.rs
env::args() produit chaque argument sous la forme d’un String, y compris le chemin de l’exécutable à l’index zéro. La fonction main préparée les rassemble dans un vecteur. Elle transmet ensuite &args au paramètre args: &[String].
Le type &[String] est une slice de valeurs String : une vue empruntée en lecture seule sur les éléments du vecteur. Il repose sur le même principe d’emprunt que &str, mais représente une séquence d’éléments String plutôt que des octets de texte. Le vecteur reste la propriété de main pendant que run lit ses éléments.
Le flux de données est le suivant :
shell words → env::args() → Vec<String> in main → borrowed &[String] in run
Remplacez le Err(...) provisoire dans run par :
if args.len() != 3 {
return Err(String::from("usage: mini-search <query> <file>"));
}
let query = args[1].clone();
let path = args[2].clone();
Ok(vec![format!("Query: {query}"), format!("File: {path}")])
Trois entrées exactement correspondent au nom de l’exécutable et aux deux arguments fournis par l’utilisateur. L’instruction explicite return Err(...) arrête immédiatement l’exécution de run lorsque cette structure est incorrecte. Il s’agit d’un retour anticipé, contrairement aux retours par expression finale utilisés dans le laboratoire sur les fonctions : le code qui suit ne s’exécute que lorsque le nombre d’arguments est valide.
L’indexation de la slice empruntée produit des chaînes empruntées. Les deux appels à clone créent volontairement des copies possédées uniquement pour la requête et le chemin, afin que le reste de run puisse les gérer comme des valeurs locales possédées. Il s’agit d’un choix délibéré concernant la propriété, et non d’une solution générale aux erreurs de déplacement. La macro vec![first, second] crée ensuite un vecteur à deux éléments, et Ok renvoie temporairement ces deux lignes d’inspection.
Enregistrez avec Ctrl+O, appuyez sur Entrée, puis quittez avec Ctrl+X. Exécutez la commande avec deux arguments :
cargo run --quiet -- rust data/notes.txt
Le premier --quiet réduit les messages de Cargo. Le -- distinct indique à Cargo de cesser d’interpréter les options ; tout ce qui le suit est transmis à votre programme.
Query: rust
File: data/notes.txt
Cela confirme que les arguments sont arrivés aux positions attendues.
Lire le fichier et appeler la logique de la bibliothèque
Dans cette étape, vous allez remplacer le résultat d’inspection provisoire par le chargement réel du fichier et les résultats de recherche.
Ouvrez le code source du binaire :
nano src/main.rs
Ajoutez ces importations sous use std::env; :
use std::fs;
use mini_search::find_lines;
Le nom du paquet mini-search devient le nom de crate Rust mini_search. Le mot-clé pub déjà présent devant find_lines rend cette fonction accessible en dehors de la crate de bibliothèque. Son importation permet au binaire d’appeler la fonction publique définie dans src/lib.rs. Le fait de conserver la logique de recherche à cet endroit sépare le traitement réutilisable des données de la gestion des arguments propre au processus. Il s’agit d’un aperçu du modèle bibliothèque/binaire et de la visibilité, qui sera étudié en détail dans les modules ultérieurs du laboratoire.
Remplacez la ligne provisoire Ok(vec![...]) par :
let contents = fs::read_to_string(&path)
.map_err(|error| format!("could not read {path}: {error}"))?;
Ok(find_lines(&query, &contents))
L’erreur de lecture conserve le chemin demandé et est propagée avec ?. En cas de réussite, le binaire emprunte la requête et le contenu du fichier pendant que la bibliothèque renvoie les lignes correspondantes possédées.
Enregistrez et quittez, puis vérifiez et exécutez le programme :
cargo check
cargo run --quiet -- rust data/notes.txt
Rust makes ownership explicit.
Cargo builds Rust packages.
Rust tools help beginners.
Les résultats normaux de la recherche sont écrits sur la sortie standard par le bras Ok déjà préparé dans main.
Transformer une recherche vide en erreur utile
Dans cette étape, vous allez distinguer une recherche réussie qui renvoie des résultats d’une recherche valide qui ne trouve rien.
Ouvrez le code source :
nano src/main.rs
Remplacez Ok(find_lines(&query, &contents)) par :
let matches = find_lines(&query, &contents);
if matches.is_empty() {
return Err(format!("no lines matched '{query}'"));
}
Ok(matches)
Un vecteur vide peut être détecté, mais la commande est plus utile lorsqu’elle explique ce résultat. Enregistrez et quittez, puis recherchez un terme absent :
cargo run --quiet -- python data/notes.txt
no lines matched 'python'
À cette étape intermédiaire, le bras Err préparé écrit encore le message sur la sortie standard et se termine avec succès. L’étape suivante donnera aux erreurs le comportement correct au niveau du processus.
Envoyer les erreurs vers stderr et retourner un statut différent de zéro
Dans cette étape, vous allez terminer l’interface de la commande en séparant la sortie normale des échecs.
La sortie standard, ou stdout, transporte les résultats demandés. La sortie d’erreur standard, ou stderr, transporte les diagnostics indépendamment. Un statut de sortie égal à zéro indique une réussite ; un statut différent de zéro indique un échec. Ouvrez le code source du binaire :
nano src/main.rs
Ajoutez cette importation sous les importations existantes de la bibliothèque standard :
use std::process;
Remplacez le bras Err(error) écrit sur une seule ligne par :
Err(error) => {
eprintln!("{error}");
process::exit(1);
}
eprintln! écrit une ligne sur stderr. process::exit(1) termine immédiatement le processus avec le statut 1. Enregistrez et quittez, puis construisez une fois le programme afin de pouvoir exécuter directement le binaire sans que Cargo ajoute son propre message d’échec :
cargo build --quiet
./target/debug/mini-search python data/notes.txt
Le diagnostic reste le suivant :
no lines matched 'python'
Affichez immédiatement le statut de la commande précédente :
echo $?
echo affiche ses arguments, et le shell remplace $? par le statut de sortie de la commande la plus récente :
1
La même interface couvre désormais les utilisations incorrectes et les fichiers absents. L’exécution de ./target/debug/mini-search affiche le message d’utilisation ; un chemin tel que data/missing.txt affiche l’erreur de lecture. Dans les deux cas, le message est écrit uniquement sur stderr et le processus se termine avec le statut 1. En revanche, une recherche qui trouve une correspondance écrit les résultats sur stdout et se termine avec le statut zéro.
Résumé
Vous avez collecté et validé des arguments de ligne de commande, utilisé le séparateur -- de Cargo, conservé la logique de recherche dans une limite de bibliothèque préparée, chargé le fichier demandé et mis en place le comportement conventionnel de stdout, stderr et du statut de sortie pour une réussite et trois cas d’échec.


