Variables d'environnement et processus en Rust
Tout programme s’exécute dans un environnement. Le système d’exploitation lui transmet un ensemble de valeurs texte nommées, comme PATH, HOME ou PORT. Ce sont les variables d’environnement. Elles offrent un moyen simple de configurer un programme sans toucher à son code : le même binaire peut utiliser le port 8080 sur votre ordinateur portable et le port 80 sur un serveur.
Un programme vit aussi au milieu d’autres programmes. Il peut se terminer avec un code de sortie qui dit à celui qui l’a lancé « ça a marché » ou « ça a échoué », et il peut lui-même lancer d’autres programmes. En Rust, les outils pour tout cela se trouvent dans deux modules standard : std::env et std::process. Aucune crate n’est nécessaire.
Lire une variable avec env::var
Section intitulée « Lire une variable avec env::var »std::env::var prend un nom et renvoie un Result. C’est Ok(String) quand la variable existe, et Err quand elle est absente (ou que ce n’est pas de l’Unicode valide). Comme une variable absente est une situation normale, Rust vous oblige à gérer ce cas :
use std::env;
fn main() { // Un nom qui n'est presque certainement défini sur aucune machine. match env::var("RUSTTUTORIAL_API_KEY") { Ok(value) => println!("Trouvée : {value}"), Err(e) => println!("Indisponible : {e}"), }
let port = env::var("RUSTTUTORIAL_PORT").unwrap_or(String::from("8080")); println!("Port : {port}");}Sortie :
Indisponible : environment variable not foundPort : 8080Le message « environment variable not found » vient de Rust lui-même : il signifie que la variable d’environnement est introuvable. Utilisez match quand vous voulez réagir au cas où elle manque. Utilisez .unwrap_or(default) quand une variable absente veut simplement dire « prends la valeur par défaut ».
Valeurs par défaut, nombres et flags
Section intitulée « Valeurs par défaut, nombres et flags »Les variables d’environnement sont toujours du texte. Pour obtenir un nombre, il faut le convertir, et la conversion peut échouer elle aussi. Un schéma courant : transformer le Result en Option avec .ok(), tenter la conversion, et revenir à une valeur par défaut si quelque chose se passe mal :
use std::env;
fn main() { let workers: u32 = env::var("RUSTTUTORIAL_WORKERS") .ok() .and_then(|text| text.parse().ok()) .unwrap_or(4);
let debug = env::var("RUSTTUTORIAL_DEBUG").is_ok();
println!("workers = {workers}"); println!("debug = {debug}");}Sortie :
workers = 4debug = false.is_ok() est un moyen rapide de traiter une variable comme un interrupteur (flag) : si elle est définie, quelle que soit sa valeur, le flag est activé.
var_os, vars et définir des variables
Section intitulée « var_os, vars et définir des variables »env::var_os renvoie une Option<OsString> au lieu d’un Result. Elle n’échoue jamais sur des octets inhabituels, ce qui la rend pratique pour les chemins. env::vars() vous donne toutes les variables sous forme de paires (nom, valeur).
env::set_var modifie une variable pour le processus en cours. Remarquez le bloc unsafe autour :
use std::env;
fn main() { unsafe { env::set_var("MYAPP_COLOR", "bleu"); env::set_var("MYAPP_SIZE", "grand"); }
println!("{:?}", env::var_os("MYAPP_COLOR")); println!("{:?}", env::var_os("MYAPP_MISSING"));
let mut mine: Vec<(String, String)> = env::vars() .filter(|(key, _)| key.starts_with("MYAPP_")) .collect(); mine.sort(); // l'ordre de env::vars() n'est pas garanti
for (key, value) in &mine { println!("{key}={value}"); }}Sortie :
Some("bleu")NoneMYAPP_COLOR=bleuMYAPP_SIZE=grandPourquoi set_var est-elle unsafe ? Sur beaucoup de systèmes d’exploitation, l’environnement est une seule table partagée par tout le processus. Si un thread la modifie pendant qu’un autre thread (ou une bibliothèque C) la lit, le programme peut planter. Rust ne peut pas vérifier ça pour vous : depuis l’édition 2024, set_var et remove_var sont donc des fonctions unsafe. En édition 2021, elles compilent encore sans unsafe, mais écrire le bloc quand même fonctionne dans les deux cas. La règle sûre : ne définissez des variables qu’au tout début de main, avant de lancer le moindre thread. Mieux encore, ne modifiez pas du tout votre propre environnement. Pour transmettre une variable à un programme enfant, utilisez Command::env (présenté plus bas). Voir Unsafe en Rust pour comprendre ce que signifie unsafe.
Le dossier courant
Section intitulée « Le dossier courant »env::current_dir() renvoie le dossier depuis lequel le programme a été lancé, sous forme de PathBuf. Les chemins relatifs comme "config.toml" sont résolus par rapport à lui. Le vrai chemin dépend de votre machine, donc cet exemple n’affiche que des faits vrais partout :
use std::env;
fn main() { let dir = env::current_dir().expect("impossible de lire le dossier courant"); println!("Le chemin est-il absolu ? {}", dir.is_absolute());
let config = dir.join("config.toml"); println!("Se termine par config.toml ? {}", config.ends_with("config.toml"));}Sortie :
Le chemin est-il absolu ? trueSe termine par config.toml ? trueAffichez dir.display() sur votre machine pour voir le chemin complet. Les arguments de ligne de commande se trouvent aussi dans std::env ; ils ont leur propre page : Arguments de ligne de commande.
Les codes de sortie avec process::exit
Section intitulée « Les codes de sortie avec process::exit »Quand un programme se termine, il renvoie un nombre à celui qui l’a lancé. 0 signifie succès ; toute autre valeur signifie échec. Les scripts shell et les systèmes de CI vérifient ce nombre. std::process::exit(code) arrête le programme immédiatement avec ce code :
use std::env;use std::process;
fn main() { println!("Démarrage...");
let token = match env::var("RUSTTUTORIAL_TOKEN") { Ok(t) => t, Err(_) => { eprintln!("erreur : RUSTTUTORIAL_TOKEN n'est pas définie"); process::exit(1); } };
println!("Utilisation du jeton {token}");}Sortie :
Démarrage...La ligne d’erreur est affichée avec eprintln! : elle part donc sur stderr, pas sur stdout. Le Playground l’affiche dans une zone séparée, avec le code de sortie.
Deux choses à savoir. D’abord, process::exit termine le programme sur-le-champ : les destructeurs (Drop) des valeurs encore vivantes ne s’exécutent pas. Ensuite, vous n’en avez souvent pas besoin : si main renvoie Result<(), E> et que vous renvoyez Err, Rust affiche l’erreur et quitte avec le code 1 pour vous.
Lancer d’autres programmes avec Command
Section intitulée « Lancer d’autres programmes avec Command »std::process::Command lance un autre programme. Vous le construisez étape par étape : le nom du programme, puis chaque argument avec .arg() ou .args(). Ensuite, vous choisissez comment l’exécuter :
.output()attend qu’il se termine et capture son stdout et son stderr..status()attend aussi qu’il se termine, mais laisse sa sortie aller directement dans votre terminal.
use std::process::Command;
fn main() { let output = Command::new("echo") .arg("Bonjour") .arg("depuis un processus enfant") .output() .expect("impossible de lancer echo");
let text = String::from_utf8_lossy(&output.stdout); println!("succès : {}", output.status.success()); println!("stdout : {}", text.trim());}Sortie :
succès : truestdout : Bonjour depuis un processus enfantoutput.stdout contient des octets bruts (Vec<u8>), car un programme peut afficher n’importe quoi. String::from_utf8_lossy les transforme en texte.
Voici .status() qui lit un code de sortie, et .env() qui transmet une variable au programme enfant uniquement :
use std::process::Command;
fn main() { let status = Command::new("sh") .args(["-c", "exit 3"]) .status() .expect("impossible de lancer sh");
println!("succès : {}", status.success()); println!("code : {:?}", status.code());
let output = Command::new("sh") .args(["-c", "echo \"$GREETING, $NAME\""]) .env("GREETING", "Salut") .env("NAME", "Ferris") .output() .expect("impossible de lancer sh"); print!("{}", String::from_utf8_lossy(&output.stdout));}Sortie :
succès : falsecode : Some(3)Salut, FerrisSous Windows : echo n’y est pas un programme séparé ; il est intégré au shell cmd, et sh n’existe généralement pas. La même idée s’écrit Command::new("cmd").args(["/C", "echo Bonjour"]). Les exemples ci-dessus fonctionnent sous Linux, macOS et dans le Rust Playground.
Tout assembler : une configuration lue dans l’environnement
Section intitulée « Tout assembler : une configuration lue dans l’environnement »Un vrai programme lit en général tous ses réglages une seule fois, au démarrage, dans une struct. Chaque réglage a une valeur par défaut raisonnable, et une valeur invalide donne un message clair au lieu d’un plantage :
use std::env;
#[derive(Debug)]struct Config { host: String, port: u16, verbose: bool,}
impl Config { fn from_env() -> Config { let host = env::var("APP_HOST").unwrap_or_else(|_| String::from("127.0.0.1"));
let port = match env::var("APP_PORT") { Ok(text) => text.parse().unwrap_or_else(|_| { println!("APP_PORT={text:?} n'est pas un nombre, on utilise 3000"); 3000 }), Err(_) => 3000, };
let verbose = matches!(env::var("APP_VERBOSE").as_deref(), Ok("1") | Ok("true"));
Config { host, port, verbose } }}
fn main() { // On simule ce qu'un utilisateur pourrait définir avant de lancer le programme. unsafe { env::set_var("APP_PORT", "quatre-vingts"); env::set_var("APP_VERBOSE", "true"); }
let config = Config::from_env(); println!("{config:?}");}Sortie :
APP_PORT="quatre-vingts" n'est pas un nombre, on utilise 3000Config { host: "127.0.0.1", port: 3000, verbose: true }En usage réel, supprimez les lignes set_var et définissez plutôt les variables dans votre shell :
APP_PORT=9000 APP_VERBOSE=1 cargo run(Sous Windows PowerShell : $env:APP_PORT="9000"; cargo run.)
Erreurs courantes
Section intitulée « Erreurs courantes »Traiter env::var comme si elle renvoyait une String. Elle renvoie un Result, car la variable n’existe peut-être pas :
let port: String = env::var("PORT");error[E0308]: mismatched types --> src/main.rs:4:24 |4 | let port: String = env::var("PORT"); | ------ ^^^^^^^^^^^^^^^^ expected `String`, found `Result<String, VarError>` | | | expected due to thisLe compilateur signale des types incompatibles : il attendait une String et a trouvé un Result<String, VarError>. La solution : décidez ce que signifie une variable absente. env::var("PORT").unwrap_or(String::from("8080")) pour une valeur par défaut, ou match pour signaler le problème.
Appeler set_var sans unsafe en édition 2024. Les nouveaux projets créés avec cargo new utilisent l’édition 2024, donc ceci échoue :
error[E0133]: call to unsafe function `set_var` is unsafe and requires unsafe block --> src/main.rs:2:5 |2 | std::env::set_var("MODE", "fast"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe functionLe compilateur explique que l’appel à la fonction unsafe set_var exige un bloc unsafe. La solution : entourez-la de unsafe { ... } et appelez-la uniquement avant le lancement de tout thread, ou utilisez Command::env pour les programmes enfants.
Mettre toute la ligne de commande dans Command::new. Command::new("echo hello") cherche un programme nommé littéralement echo hello, qui n’existe pas. .output() renvoie Err(Os { code: 2, kind: NotFound, message: "No such file or directory" }) (fichier ou dossier introuvable). La solution : le nom du programme va dans new, et chaque argument dans .arg() ou .args() : Command::new("echo").arg("hello").
Récapitulatif
Section intitulée « Récapitulatif »env::var("NOM")renvoieResult<String, VarError>. Gérez le cas où la variable manque avecmatch,.unwrap_or()ou.ok().- Les valeurs d’environnement sont du texte. Convertissez vous-même les nombres et revenez à une valeur par défaut si la conversion échoue.
env::set_varestunsafeen édition 2024, car l’environnement est partagé par tous les threads. PréférezCommand::envpour les programmes enfants.process::exit(code)termine le programme sur-le-champ avec un code de sortie :0pour un succès, toute autre valeur pour un échec.Command::new("programme").args([...])lance un autre programme..output()capture ce qu’il affiche ;.status()vous donne son code de sortie.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Que renvoie env::var("PORT") quand PORT n'est pas définie ?
2. Pourquoi env::set_var est-elle une fonction unsafe en édition 2024 ?
3. Quel code de sortie signifie traditionnellement « succès » ?
4. Comment lancer ls -l avec Command ?
Score : 0 / 4
Pour aller plus loin
Section intitulée « Pour aller plus loin »- std::env — bibliothèque standard de Rust (en anglais) : toutes les fonctions pour les variables, les arguments et les dossiers.
- Working with Environment Variables — The Rust Book (en anglais) : le projet du Book lit un réglage dans l’environnement.
- std::process::Command (en anglais) : toutes les options pour lancer des programmes, avec des exemples pour Windows et Unix.
- Newly unsafe functions — Edition Guide (en anglais) : l’explication officielle de la raison pour laquelle
set_varest devenueunsafe. - Child processes — Rust by Example (en anglais) : d’autres petits exemples pour lancer des programmes et utiliser des pipes.