環境変数の使い方

Beginner

This tutorial is from open-source community. Access the source code

はじめに

環境変数の使い方へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。

この実験では、ユーザーが環境変数を使って大文字小文字を区別しない検索を有効にできるようにminigrepを強化します。これにより、ターミナルセッション内のすべての検索にこの機能を適用する便利な方法が提供されます。

環境変数の使い方

minigrepを改良して、追加機能を 1 つ付けます。ユーザーが環境変数を通じて有効にできる大文字小文字を区別しない検索オプションです。この機能をコマンドラインオプションにすることもできますし、ユーザーがそれを適用したいたびに入力する必要があります。しかし、環境変数にすることで、ユーザーは 1 回環境変数を設定するだけで、そのターミナルセッション内のすべての検索が大文字小文字を区別しなくなります。

大文字小文字を区別しない検索関数の失敗テストの作成

まず、環境変数に値がある場合に呼び出される新しいsearch_case_insensitive関数を追加します。TDD プロセスに従い続けるので、最初のステップは再び失敗するテストを書くことです。新しいsearch_case_insensitive関数に対する新しいテストを追加し、2 つのテスト間の違いを明確にするために、古いテストをone_resultからcase_sensitiveにリネームします。リスト 12-20 に示すようになります。

ファイル名:src/lib.rs

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}

リスト 12-20: 追加する大文字小文字を区別しない関数に対する新しい失敗テストの追加

古いテストのcontentsも編集したことに注意してください。大文字のD"Duct tape."という文字列を含む新しい行を追加しました。これは、大文字小文字を区別して検索する場合、クエリ"duct"と一致しないはずです。このように古いテストを変更することで、既に実装した大文字小文字を区別した検索機能が偶発的に破壊されないことを確認できます。このテストは現在通るはずで、大文字小文字を区別しない検索の作業中も通り続けるはずです。

大文字小文字を区別しない検索の新しいテストでは、クエリとして"rUsT"を使用しています。追加するsearch_case_insensitive関数では、クエリ"rUsT"は、大文字のR"Rust:"を含む行と一致し、"Trust me."の行とも一致するはずです。両方ともクエリとは大文字小文字が異なります。これが失敗するテストで、search_case_insensitive関数をまだ定義していないため、コンパイルに失敗します。リスト 12-16 のsearch関数と同じように、常に空のベクトルを返すスケルトン実装を追加して、テストがコンパイルされて失敗するのを確認しても構いません。

search_case_insensitive関数の実装

リスト 12-21 に示すsearch_case_insensitive関数は、search関数とほぼ同じになります。唯一の違いは、queryと各lineを小文字に変換することで、入力引数のケースに関係なく、行がクエリを含むかどうかをチェックする際に、ケースが同じになるようにすることです。

ファイル名:src/lib.rs

pub fn search_case_insensitive<'a>(
    query: &str,
    contents: &'a str,
) -> Vec<&'a str> {
  1 let query = query.to_lowercase();
    let mut results = Vec::new();

    for line in contents.lines() {
        if 2 line.to_lowercase().contains(3 &query) {
            results.push(line);
        }
    }

    results
}

リスト 12-21: クエリと行を比較する前に、クエリと行を小文字にするsearch_case_insensitive関数を定義する

まず、query文字列を小文字に変換し、同じ名前のシャドウ変数に格納します[1]。クエリにto_lowercaseを呼び出すことは必要です。ユーザーのクエリが"rust""RUST""Rust"、または"rUsT"のいずれであっても、クエリを"rust"として扱い、ケースに関係なくなるようにするためです。to_lowercaseは基本的な Unicode を処理しますが、100% 正確ではありません。本当のアプリケーションを書く場合、ここでもう少し作業が必要になりますが、このセクションは環境変数に関するものであり、Unicode ではないので、ここではそれにとどめます。

queryは現在、文字列スライスではなくStringになっています。なぜなら、to_lowercaseを呼び出すと新しいデータが作成されるため、既存のデータを参照するわけではないからです。例えば、クエリが"rUsT"の場合、その文字列スライスには小文字のutが含まれておらず、使用できません。そのため、"rust"を含む新しいStringを割り当てる必要があります。現在、querycontainsメソッドの引数として渡す際には、アンパサンドを付ける必要があります[3]。なぜなら、containsのシグネチャは文字列スライスを取るように定義されているからです。

次に、各lineto_lowercaseを呼び出して、すべての文字を小文字に変換します[2]。これでlinequeryが小文字になったので、クエリのケースに関係なく一致するものを見つけることができます。

この実装がテストに合格するかどうか見てみましょう:

running 2 tests
test tests::case_insensitive... ok
test tests::case_sensitive... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s

素晴らしい!合格しました。次に、run関数から新しいsearch_case_insensitive関数を呼び出しましょう。まず、Config構造体にコンフィグオプションを追加して、大文字小文字を区別する検索と大文字小文字を区別しない検索の間で切り替えられるようにします。このフィールドを追加すると、コンパイラエラーが発生します。なぜなら、まだどこでもこのフィールドを初期化していないからです:

ファイル名:src/lib.rs

pub struct Config {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

ignore_caseフィールドを追加しました。これはブール値を保持します。次に、run関数がignore_caseフィールドの値をチェックし、それを使ってsearch関数かsearch_case_insensitive関数を呼び出すかを決定する必要があります。リスト 12-22 に示すようになります。これでもまだコンパイルされません。

ファイル名:src/lib.rs

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.file_path)?;

    let results = if config.ignore_case {
        search_case_insensitive(&config.query, &contents)
    } else {
        search(&config.query, &contents)
    };

    for line in results {
        println!("{line}");
    }

    Ok(())
}

リスト 12-22: config.ignore_caseの値に基づいてsearchまたはsearch_case_insensitiveを呼び出す

最後に、環境変数をチェックする必要があります。環境変数を操作する関数は、標準ライブラリのenvモジュールにあります。そのため、src/lib.rsの先頭でそのモジュールをスコープに持ち込みます。そして、envモジュールのvar関数を使って、IGNORE_CASEという名前の環境変数に値が設定されているかどうかをチェックします。リスト 12-23 に示すようになります。

ファイル名:src/lib.rs

use std::env;
--snip--

impl Config {
    pub fn build(
        args: &[String]
    ) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let file_path = args[2].clone();

        let ignore_case = env::var("IGNORE_CASE").is_ok();

        Ok(Config {
            query,
            file_path,
            ignore_case,
        })
    }
}

リスト 12-23: IGNORE_CASEという名前の環境変数に値が設定されているかどうかをチェックする

ここで、新しい変数ignore_caseを作成します。その値を設定するには、env::var関数を呼び出し、IGNORE_CASE環境変数の名前を渡します。env::var関数はResultを返します。環境変数が何らかの値に設定されている場合、それは環境変数の値を含む成功したOkバリアントになります。環境変数が設定されていない場合、Errバリアントが返されます。

Resultis_okメソッドを使って、環境変数が設定されているかどうかをチェックしています。これは、プログラムが大文字小文字を区別しない検索を行う必要があることを意味します。IGNORE_CASE環境変数が何も設定されていない場合、is_okfalseを返し、プログラムは大文字小文字を区別した検索を行います。環境変数の値には関係なく、設定されているかどうかだけを見ているので、unwrapexpect、またはResultで見た他のメソッドを使わずに、is_okをチェックしています。

ignore_case変数の値をConfigインスタンスに渡します。そうすることで、run関数はその値を読み取り、リスト 12-22 で実装したように、search_case_insensitiveまたはsearchを呼び出すかを決定できます。

試してみましょう!まず、環境変数を設定せずに、クエリtoでプログラムを実行します。これは、小文字のtoを含む任意の行と一致するはずです:

$ cargo run -- to poem.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!

まだ機能しているようです!次に、IGNORE_CASE1に設定して、同じクエリtoでプログラムを実行します:

IGNORE_CASE=1 cargo run -- to poem.txt

PowerShell を使っている場合は、環境変数を設定してプログラムを個別のコマンドで実行する必要があります:

PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt

これにより、IGNORE_CASEがシェルセッションの残りの間持続します。Remove-Itemコマンドレットで解除できます:

PS> Remove-Item Env:IGNORE_CASE

大文字のtoを含む行が得られるはずです:

Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!

素晴らしい!Toを含む行も得られました!minigrepプログラムは、環境変数によって制御される大文字小文字を区別しない検索ができるようになりました。これで、コマンドライン引数または環境変数を使って設定されるオプションを管理する方法がわかりました。

一部のプログラムでは、同じコンフィグに対して引数と環境変数の両方を許可しています。その場合、プログラムはどちらか一方が優先するように決定します。独自の演習として、コマンドライン引数または環境変数を通じて大文字小文字の区別を制御してみてください。プログラムを大文字小文字を区別するように設定し、大文字小文字を区別しないように設定した場合、コマンドライン引数と環境変数のどちらが優先するかを決定してみてください。

std::envモジュールには、環境変数を扱うためのさらに多くの便利な機能があります。利用可能な機能を確認するには、そのドキュメントを参照してください。

まとめ

おめでとうございます!「環境変数の使い方」の実験を完了しました。LabEx でさらに多くの実験を行って、スキルを向上させましょう。