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

いつものように、cargo newで新しいプロジェクトを作りましょう。プロジェクトをminigrepと名付けて、 既に自分のシステムに存在するかもしれないgrepツールと区別しましょう。

最初の仕事は、minigrepを二つの引数を受け付けるようにすることです: ファイル名と検索する文字列ですね。 つまり、cargo runで検索文字列と検索を行うファイルへのパスと共にプログラムを実行できるようになりたいということです。 こんな感じにね:

$ cargo run searchstring example-filename.txt

今現在は、cargo newで生成されたプログラムは、与えた引数を処理できません。 Crates.ioに存在するある既存のライブラリは、 コマンドライン引数を受け付けるプログラムを書く手助けをしてくれますが、ちょうどこの概念を学んでいる最中なので、 この能力を自分で実装しましょう。

引数の値を読み取る

minigrepが渡したコマンドライン引数の値を読み取れるようにするために、Rustの標準ライブラリで提供されている関数が必要になり、 それは、std::env::argsです。この関数は、minigrepに与えられたコマンドライン引数のイテレータを返します。 イテレータについてはまだ議論していません(完全には第13章で解説します)が、とりあえずイテレータに関しては、 2つの詳細のみ知っていればいいです: イテレータは一連の値を生成することと、イテレータに対してcollect関数を呼び出し、 イテレータが生成する要素全部を含むベクタ型などのコレクションに変えられることです。

リスト12-1のコードを使用してminigrepプログラムに渡されたあらゆるコマンドライン引数を読み取れるようにし、 それからその値をベクタ型として集結させてください。

ファイル名: src/main.rs

use std::env;

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

リスト12-1: コマンドライン引数をベクタ型に集結させ、出力する

まず、std::envモジュールをuse文でスコープに導入したので、args関数が使用できます。 std::env::args関数は、2レベルモジュールがネストされていることに気付いてください。 第7章で議論したように、希望の関数が2モジュール以上ネストされている場合、 関数ではなく親モジュールをスコープに導入するのが因習的です。そうすることで、 std::envから別の関数も容易に使用することができます。また、 use std::env::argsを追記し、関数をargsとするだけで呼び出すのに比べて曖昧でもありません。 というのも、argsは現在のモジュールに定義されている関数と容易に見間違えられるかもしれないからです。

args関数と不正なユニコード

引数のどれかが不正なユニコードを含んでいたら、std::env::argsはパニックすることに注意してください。 プログラムが不正なユニコードを含む引数を受け付ける必要があるなら、代わりにstd::env::args_osを使用してください。 この関数は、String値ではなく、OsString値を生成するイテレータを返します。ここでは、 簡潔性のためにstd::env::argsを使うことを選択しました。 なぜなら、OsString値はプラットフォームごとに異なり、String値に比べて取り扱いが煩雑だからです。

mainの最初の行でenv::argsを呼び出し、そして即座にcollectを使用して、 イテレータをイテレータが生成する値全てを含むベクタ型に変換しています。 collect関数を使用して多くの種類のコレクションを生成することができるので、 argsの型を明示的に注釈して文字列のベクタが欲しいのだと指定しています。Rustにおいて、 型を注釈しなければならない頻度は非常に少ないのですが、collectはよく確かに注釈が必要になる一つの関数なのです。 コンパイラには、あなたが欲しているコレクションの種類が推論できないからです。

最後に、デバッグ整形機の:?を使用してベクタを出力しています。引数なしでコードを走らせてみて、 それから引数二つで試してみましょう:

$ cargo run
--snip--
["target/debug/minigrep"]

$ cargo run needle haystack
--snip--
["target/debug/minigrep", "needle", "haystack"]

ベクタの最初の値は"target/debug/minigrep"であり、これはバイナリの名前であることに気付いてください。 これはCの引数リストの振る舞いと合致し、実行時に呼び出された名前をプログラムに使わせてくれるわけです。 メッセージで出力したり、プログラムを起動するのに使用されたコマンドラインエイリアスによってプログラムの振る舞いを変えたい場合に、 プログラム名にアクセスするのにしばしば便利です。ですが、この章の目的には、これを無視し、必要な二つの引数のみを保存します。

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

引数のベクタの値を出力すると、プログラムはコマンドライン引数として指定された値にアクセスできることが具現化されました。 さて、プログラムの残りを通して使用できるように、二つの引数の値を変数に保存する必要があります。 それをしているのがリスト12-2です。

ファイル名: src/main.rs

use std::env;

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

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

    // {}を探しています
    println!("Searching for {}", query);
    // {}というファイルの中
    println!("In file {}", filename);
}

リスト12-2: クエリ引数とファイル名引数を保持する変数を生成

ベクタを出力した時に確認したように、プログラム名がベクタの最初の値、args[0]を占めているので、 添字1から始めます。minigrepが取る最初の引数は、検索する文字列なので、 最初の引数への参照を変数queryに置きました。2番目の引数はファイル名でしょうから、 2番目の引数への参照は変数filenameに置きました。

一時的にこれら変数の値を出力して、コードが意図通りに動いていることを証明しています。 再度このプログラムをtestsample.txtという引数で実行しましょう:

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

素晴らしい、プログラムは動作しています!必要な引数の値が、正しい変数に保存されています。後ほど、 何らかのエラー処理を加えて、特定の可能性のあるエラー状況に対処します。ユーザが引数を提供しなかった場合などです; 今は、そのような状況はないものとし、代わりにファイル読み取り能力を追加することに取り組みます。