Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

コマンドライン引数を受け取る

いつものように、cargo new を使って新しいプロジェクトを作成しましょう。すでにシステム上にあるかもしれない grep ツールと区別するために、プロジェクト名は minigrep にします。

$ cargo new minigrep
     Created binary (application) `minigrep` project
$ cd minigrep

最初のタスクは、minigrep が 2 つのコマンドライン引数、つまりファイルパスと検索対象の文字列を受け取れるようにすることです。つまり、cargo run に続けて、後続の引数が cargo 用ではなく自分たちのプログラム用であることを示す 2 つのハイフン、検索する文字列、そして検索対象ファイルへのパスを指定して、次のようにプログラムを実行できるようにしたいのです。

$ cargo run -- searchstring example-filename.txt

現時点では、cargo new によって生成されたプログラムは、渡された引数を処理できません。crates.io には、コマンドライン引数を受け取るプログラムを書くのを助けてくれる既存のライブラリもありますが、いまはこの概念を学んでいるところなので、この機能は自分たちで実装してみましょう。

引数の値を読み取る

minigrep が渡されたコマンドライン引数の値を読み取れるようにするには、Rust の標準ライブラリが提供する std::env::args 関数が必要です。この関数は、minigrep に渡されたコマンドライン引数のイテレータを返します。イテレータについては 第 13 章 で詳しく扱います。今のところは、イテレータについて 2 つの点だけ知っておけば十分です。イテレータは一連の値を生成すること、そしてイテレータに対して collect メソッドを呼び出すことで、生成されたすべての要素を含むベクタのようなコレクションに変換できることです。

リスト 12-1 のコードでは、minigrep プログラムが渡された任意のコマンドライン引数を読み取り、その値をベクタに集められるようにしています。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    dbg!(args);
}

まず、use 文で std::env モジュールをスコープに導入し、その args 関数を使えるようにします。std::env::args 関数は 2 段階のモジュールの中にネストされていることに注目してください。第 7 章 で説明したように、目的の関数が複数のモジュールにまたがってネストされている場合、私たちは関数そのものではなく親モジュールをスコープに導入することを選んでいます。そうすることで、std::env からほかの関数も簡単に使えます。また、use std::env::args を追加してから単に args として関数を呼び出すよりも曖昧さが少なくなります。というのも、args は現在のモジュール内で定義された関数と簡単に取り違えられる可能性があるからです。

args 関数と無効な Unicode

std::env::args は、いずれかの引数に無効な Unicode が含まれていると panic することに注意してください。プログラムで無効な Unicode を含む引数を受け取る必要がある場合は、代わりに std::env::args_os を使ってください。この関数は、String 値ではなく OsString 値を生成するイテレータを返します。ここでは、簡潔さを優先して std::env::args を使うことにしました。というのも、OsString の値はプラットフォームごとに異なり、String 値よりも扱いが複雑だからです。

main の最初の行で env::args を呼び出し、そのイテレータを、生成されるすべての値を含むベクタへ変換するために、すぐに collect を使っています。collect 関数は多くの種類のコレクションを作るために使えるので、文字列のベクタが欲しいことを指定するために、args の型を明示的に注釈しています。Rust では型注釈が必要になることはごくまれですが、collect は、Rust がどの種類のコレクションを望んでいるのか推論できないため、しばしば型注釈が必要になる関数の 1 つです。

最後に、デバッグマクロを使ってベクタを表示します。まずは引数なしでコードを実行し、その後で 2 つの引数を付けて実行してみましょう。

$ cargo run
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/minigrep`
[src/main.rs:5:5] args = [
    "target/debug/minigrep",
]
$ cargo run -- needle haystack
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
     Running `target/debug/minigrep needle haystack`
[src/main.rs:5:5] args = [
    "target/debug/minigrep",
    "needle",
    "haystack",
]

ベクタの最初の値が "target/debug/minigrep" になっていることに注目してください。これは私たちのバイナリの名前です。これは C における引数リストの振る舞いと一致しており、プログラムは実行時にどの名前で呼び出されたかを利用できます。メッセージに表示したい場合や、どのコマンドラインエイリアスで起動されたかに応じてプログラムの振る舞いを変えたい場合には、プログラム名にアクセスできると便利なことがよくあります。しかしこの章では、それを無視して、必要な 2 つの引数だけを保存します。

引数の値を変数に保存する

現在、このプログラムはコマンドライン引数として指定された値にアクセスできます。次は、その 2 つの引数の値を変数に保存して、プログラムの残りの部分全体でその値を使えるようにする必要があります。これをリスト 12-2 で行います。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();

    let query = &args[1];
    let file_path = &args[2];

    println!("Searching for {query}");
    println!("In file {file_path}");
}

ベクタを表示したときに見たように、プログラム名が args[0] にあるベクタの最初の値を占めるため、引数はインデックス 1 から使い始めます。minigrep が受け取る最初の引数は検索したい文字列なので、最初の引数への参照を変数 query に入れます。2 番目の引数はファイルパスなので、2 番目の引数への参照を変数 file_path に入れます。

コードが意図どおりに動作していることを確認するために、これらの変数の値を一時的に表示します。このプログラムを、今度は testsample.txt という引数付きで再び実行してみましょう。

$ cargo run -- test sample.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt

すばらしいことに、このプログラムは動いています。必要な引数の値は、正しい変数に保存されています。後で、ユーザーが引数をまったく指定しなかった場合のような、起こりうる誤った状況に対処するためのエラーハンドリングを追加します。今はその状況は無視して、代わりにファイル読み取り機能を追加する作業に進みましょう。