Rustの環境変数とプロセス
どんなプログラムも、ある環境の中で動いています。OSはプログラムに、PATH、HOME、PORT のような名前付きのテキストの値をひとそろい渡します。これが環境変数(environment variable)です。環境変数を使うと、コードを書き換えずにプログラムの設定を変えられます。同じバイナリが、手元のノートPCではポート8080を、サーバーではポート80を使う、といったことができるわけです。
また、プログラムはほかのプログラムに囲まれて動いています。終わるときには、呼び出し元に「うまくいった」か「失敗した」かを伝える終了コード(exit code)を返せますし、自分でほかのプログラムを起動することもできます。Rustでは、これらの道具は標準ライブラリの2つのモジュール、std::env と std::process にそろっています。クレートは必要ありません。
env::varで変数を読む
Section titled “env::varで変数を読む”std::env::var は名前を受け取り、Result を返します。変数があれば Ok(String)、なければ(あるいは正しいUnicodeでなければ)Err です。変数が設定されていないのはよくあることなので、Rustはそのケースの処理を求めてきます。
use std::env;
fn main() { // どのマシンでもまず設定されていない名前 match env::var("RUSTTUTORIAL_API_KEY") { Ok(value) => println!("見つかりました: {value}"), Err(e) => println!("使えません: {e}"), }
let port = env::var("RUSTTUTORIAL_PORT").unwrap_or(String::from("8080")); println!("ポート: {port}");}実行結果:
使えません: environment variable not foundポート: 8080environment variable not found(環境変数が見つからない)の部分は、Rustの標準ライブラリが用意しているエラーメッセージなので英語のままです。
変数がないケースに対応したいときは match を使いましょう。「なければデフォルト値を使う」だけでよいなら .unwrap_or(default) が便利です。
デフォルト値、数値、フラグ
Section titled “デフォルト値、数値、フラグ”環境変数はいつもテキストです。数値として使うにはパース(変換)する必要があり、パースも失敗することがあります。よく使う書き方は、.ok() で Result を Option に変え、パースを試し、どこかで失敗したらデフォルト値に戻す、というものです。
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}");}実行結果:
workers = 4debug = false.is_ok() を使うと、変数をオン/オフのフラグとして手軽に扱えます。とにかく設定されていれば、フラグはオンです。
var_os、vars、変数の設定
Section titled “var_os、vars、変数の設定”env::var_os は Result ではなく Option<OsString> を返します。おかしなバイト列でも失敗しないので、パスを扱うときに便利です。env::vars() は、すべての変数を (名前, 値) のペアとして返します。
env::set_var は、いま動いているプロセスの変数を変更します。まわりにある unsafe ブロックに注目してください。
use std::env;
fn main() { unsafe { env::set_var("MYAPP_COLOR", "blue"); env::set_var("MYAPP_SIZE", "large"); }
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(); // env::vars() の順番は保証されていない
for (key, value) in &mine { println!("{key}={value}"); }}実行結果:
Some("blue")NoneMYAPP_COLOR=blueMYAPP_SIZE=largeなぜ set_var は unsafe なのでしょうか? 多くのOSでは、環境変数はプロセス全体で共有される1つの表です。あるスレッドがそれを書き換えている最中に、別のスレッド(やCのライブラリ)が読むと、プログラムがクラッシュすることがあります。Rustにはこれをチェックできないので、edition 2024 からは set_var と remove_var が unsafe な関数になりました。edition 2021では unsafe なしでもコンパイルできますが、ブロックを書いておけばどちらでも動きます。安全に使うルールは、変数を設定するのは main のいちばん最初、スレッドを起動する前だけにすることです。さらに言えば、自分自身の環境はそもそも変更しないのがいちばんです。子プログラムに変数を渡したいなら、後で紹介する Command::env を使いましょう。unsafe の意味については Rustのunsafe を見てください。
カレントディレクトリ
Section titled “カレントディレクトリ”env::current_dir() は、プログラムが起動されたフォルダを PathBuf で返します。"config.toml" のような相対パスは、このフォルダを基準に解決されます。実際のパスはマシンによって違うので、この例ではどこでも成り立つことだけを表示しています。
use std::env;
fn main() { let dir = env::current_dir().expect("カレントディレクトリを読み取れませんでした"); println!("絶対パスですか? {}", dir.is_absolute());
let config = dir.join("config.toml"); println!("config.tomlで終わっていますか? {}", config.ends_with("config.toml"));}実行結果:
絶対パスですか? trueconfig.tomlで終わっていますか? true自分のマシンで dir.display() を表示すると、フルパスを確認できます。コマンドライン引数も std::env にありますが、こちらは専用のページがあります。Rustのコマンドライン引数を見てください。
process::exitで終了コードを返す
Section titled “process::exitで終了コードを返す”プログラムは終わるときに、起動した側へ数値を1つ返します。0 は成功、それ以外は失敗という意味です。シェルスクリプトやCIシステムは、この数値をチェックしています。std::process::exit(code) は、その場ですぐにプログラムを止め、指定したコードを返します。
use std::env;use std::process;
fn main() { println!("起動しています...");
let token = match env::var("RUSTTUTORIAL_TOKEN") { Ok(t) => t, Err(_) => { eprintln!("エラー: RUSTTUTORIAL_TOKEN が設定されていません"); process::exit(1); } };
println!("トークン {token} を使います");}実行結果:
起動しています...エラーの行は eprintln! で表示しているので、stdoutではなくstderr(標準エラー出力)に出ます。Playgroundでは、終了ステータスといっしょに別の欄に表示されます。
知っておきたいことが2つあります。1つ目に、process::exit はプログラムを即座に終わらせるので、まだ生きている値のデストラクタ(Drop)は実行されません。2つ目に、たいていは使わなくても済みます。main が Result<(), E> を返すようにして Err を返せば、Rustがエラーを表示して、コード 1 で終了してくれます。
Commandでほかのプログラムを実行する
Section titled “Commandでほかのプログラムを実行する”std::process::Command は、別のプログラムを起動します。プログラム名を指定し、引数を .arg() や .args() で1つずつ追加して、少しずつ組み立てていきます。それから実行のしかたを選びます。
.output()は終わるまで待ち、stdoutとstderrをキャプチャ(取得)します。.status()も終わるまで待ちますが、出力はそのままターミナルに表示されます。
use std::process::Command;
fn main() { let output = Command::new("echo") .arg("こんにちは") .arg("子プロセスより") .output() .expect("echoを実行できませんでした");
let text = String::from_utf8_lossy(&output.stdout); println!("成功: {}", output.status.success()); println!("stdout: {}", text.trim());}実行結果:
成功: truestdout: こんにちは 子プロセスよりoutput.stdout は生のバイト列(Vec<u8>)です。プログラムは何でも出力できるからです。String::from_utf8_lossy でテキストに変換しています。
次は、.status() で終了コードを読み取る例と、.env() で子プロセスにだけ変数を渡す例です。
use std::process::Command;
fn main() { let status = Command::new("sh") .args(["-c", "exit 3"]) .status() .expect("shを実行できませんでした");
println!("成功: {}", status.success()); println!("コード: {:?}", status.code());
let output = Command::new("sh") .args(["-c", "echo \"$GREETING, $NAME\""]) .env("GREETING", "Hi") .env("NAME", "Ferris") .output() .expect("shを実行できませんでした"); print!("{}", String::from_utf8_lossy(&output.stdout));}実行結果:
成功: falseコード: Some(3)Hi, FerrisWindowsの場合: Windowsでは echo は独立したプログラムではなく cmd シェルに組み込まれたコマンドで、sh もふつうはありません。同じことをするなら Command::new("cmd").args(["/C", "echo Hello"]) のようになります。上の例は、Linux、macOS、Rust Playgroundで動きます。
まとめて使う:環境変数から設定を読む
Section titled “まとめて使う:環境変数から設定を読む”実際のプログラムでは、起動時にすべての設定を一度に読み込んで、構造体にまとめるのがふつうです。それぞれの設定には妥当なデフォルト値を用意し、おかしな値が来たらクラッシュではなくわかりやすいメッセージを出します。
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:?} は数値ではないので、3000を使います"); 3000 }), Err(_) => 3000, };
let verbose = matches!(env::var("APP_VERBOSE").as_deref(), Ok("1") | Ok("true"));
Config { host, port, verbose } }}
fn main() { // ユーザーがプログラムを実行する前に設定しそうな値を再現する unsafe { env::set_var("APP_PORT", "eighty"); env::set_var("APP_VERBOSE", "true"); }
let config = Config::from_env(); println!("{config:?}");}実行結果:
APP_PORT="eighty" は数値ではないので、3000を使いますConfig { host: "127.0.0.1", port: 3000, verbose: true }実際に使うときは set_var の行を消して、代わりにシェルで変数を設定します。
APP_PORT=9000 APP_VERBOSE=1 cargo run(Windows PowerShellなら $env:APP_PORT="9000"; cargo run です。)
よくある間違い
Section titled “よくある間違い”env::var が String を返すと思ってしまう。 変数が存在しないかもしれないので、返ってくるのは Result です。
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 thisコンパイラは「型が合わない。String を期待していたのに Result<String, VarError> が見つかった」と言っています。直し方:変数がないときにどうするかを決めましょう。デフォルト値を使うなら env::var("PORT").unwrap_or(String::from("8080"))、報告したいなら match です。
edition 2024で unsafe なしに set_var を呼ぶ。 cargo new で作った新しいプロジェクトはedition 2024を使うので、これはエラーになります。
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 function「unsafeな関数の呼び出しにはunsafeブロックが必要」というエラーです。直し方:unsafe { ... } で囲み、スレッドを起動する前にだけ呼びましょう。子プログラムに渡すだけなら Command::env を使います。
コマンドライン全体を Command::new に入れてしまう。 Command::new("echo hello") は、文字どおり echo hello という名前のプログラムを探しますが、そんなものはありません。.output() は Err(Os { code: 2, kind: NotFound, message: "No such file or directory" })(ファイルが見つからない)を返します。直し方:プログラム名は new に、引数はすべて .arg() か .args() に渡します。Command::new("echo").arg("hello") のように書きましょう。
env::var("NAME")はResult<String, VarError>を返す。変数がないケースはmatch、.unwrap_or()、.ok()で処理する。- 環境変数の値はテキスト。数値は自分でパースし、失敗したらデフォルト値に戻す。
- 環境変数はすべてのスレッドで共有されているため、edition 2024では
env::set_varはunsafe。子プログラムにはCommand::envを使うのがおすすめ。 process::exit(code)は終了コードを返してすぐにプログラムを終わらせる。0は成功、それ以外は失敗。Command::new("program").args([...])でほかのプログラムを実行する。.output()は出力をキャプチャし、.status()は終了コードを返す。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. PORT が設定されていないとき、env::var("PORT") は何を返しますか?
2. edition 2024で env::set_var が unsafe な関数になっているのはなぜですか?
3. 伝統的に「成功」を意味する終了コードはどれですか?
4. Command で ls -l を実行するにはどう書きますか?
スコア: 0 / 4
さらに詳しく
Section titled “さらに詳しく”- std::env — Rust標準ライブラリ(英語):環境変数、引数、ディレクトリを扱う関数がすべて載っています。
- 環境変数を取り扱う — The Rust Programming Language 日本語版:The Bookのプロジェクトで、設定を環境変数から読み込みます。
- std::process::Command(英語):プログラムを起動するためのオプションが、WindowsとUnixの例つきでそろっています。
- Newly unsafe functions — Edition Guide(英語):
set_varがunsafeになった理由の公式な説明です。 - 子プロセス — Rust By Example 日本語版:プログラムの実行やパイプの使い方の短い例がさらに見られます。