Работа с переменными окружения

RustRustBeginner
Практиковаться сейчас

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

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Добро пожаловать в Работу с переменными окружения. Этот практикум является частью Книги по Rust. Вы можете практиковать свои навыки Rust в LabEx.

В этом практикуме мы усовершенствуем minigrep, позволяя пользователям включать нечувствительность к регистру при поиске с помощью переменной окружения, что обеспечивает удобный способ применения этой функции к всем их поискам в терминальном сеансе.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/ControlStructuresGroup(["Control Structures"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/ControlStructuresGroup -.-> rust/for_loop("for Loop") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/traits("Traits") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100422{{"Работа с переменными окружения"}} rust/mutable_variables -.-> lab-100422{{"Работа с переменными окружения"}} rust/string_type -.-> lab-100422{{"Работа с переменными окружения"}} rust/for_loop -.-> lab-100422{{"Работа с переменными окружения"}} rust/function_syntax -.-> lab-100422{{"Работа с переменными окружения"}} rust/expressions_statements -.-> lab-100422{{"Работа с переменными окружения"}} rust/method_syntax -.-> lab-100422{{"Работа с переменными окружения"}} rust/traits -.-> lab-100422{{"Работа с переменными окружения"}} rust/operator_overloading -.-> lab-100422{{"Работа с переменными окружения"}} end

Работа с переменными окружения

Мы усовершенствуем minigrep, добавив дополнительную функцию: возможность нечувствительного к регистру поиска, которую пользователь может включить с помощью переменной окружения. Мы могли бы сделать эту функцию параметром командной строки и требовать, чтобы пользователи вводили ее каждый раз, когда хотят ее применить, но, сделав ее переменной окружения, мы позволяем нашим пользователям установить переменную окружения один раз и сделать все их поиски нечувствительными к регистру в этом терминальном сеансе.

Создание провального теста для функции нечувствительного к регистру поиска

Сначала мы добавим новую функцию search_case_insensitive, которая будет вызываться, когда переменная окружения имеет значение. Мы продолжим следовать процессу TDD, поэтому первый шаг — снова написать провальный тест. Мы добавим новый тест для новой функции search_case_insensitive и переименуем старый тест из one_result в case_sensitive, чтобы прояснить различия между двумя тестами, как показано в Listing 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)
        );
    }
}

Listing 12-20: Добавление нового провального теста для функции нечувствительного к регистру, которую мы собираемся добавить

Обратите внимание, что мы также отредактировали содержимое старого теста. Мы добавили новую строку с текстом "Duct tape." с заглавной буквой D, которая не должна совпадать с запросом "duct" при поиске в чувствительности к регистру. Изменение старого теста таким образом помогает убедиться, что мы не случайно не сломаем функциональность чувствительного к регистру поиска, которую мы уже реализовали. Этот тест должен пройти сейчас и должен продолжать проходить при работе над нечувствительным к регистру поиском.

Новый тест для нечувствительного к регистру поиска использует "rUsT" в качестве запроса. В функции search_case_insensitive, которую мы собираемся добавить, запрос "rUsT" должен совпадать с строкой, содержащей "Rust:" с заглавной буквой R, и совпадать с строкой "Trust me.", хотя обе имеют разный регистр по сравнению с запросом. Это наш провальный тест, и он не скомпилируется, потому что мы еще не определили функцию search_case_insensitive. Не стесняйтесь добавить скелетную реализацию, которая всегда возвращает пустой вектор, аналогично тому, как мы это сделали для функции search в Listing 12-16, чтобы увидеть, как тест скомпилируется и упадет.

Реализация функции search_case_insensitive

Функция search_case_insensitive, показанная в Listing 12-21, будет почти такой же, как функция 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
}

Listing 12-21: Определение функции search_case_insensitive для приведения запроса и строки к нижнему регистру перед их сравнением

Сначала мы приводим строку query к нижнему регистру и сохраняем ее в переменную с тем же именем, но в новой области видимости [1]. Вызов to_lowercase для запроса необходим, чтобы независимо от регистра запроса пользователя ("rust", "RUST", "Rust" или "rUsT"), мы трактовали запрос как "rust" и были нечувствительны к регистру. Хотя to_lowercase обрабатывает базовый Unicode, он не будет 100% точным. Если бы мы писали реальное приложение, мы хотели бы сделать здесь немного больше работы, но данная глава посвящена переменным окружения, а не Unicode, поэтому мы оставим это так.

Обратите внимание, что query теперь является String, а не строковым срезом, потому что вызов to_lowercase создает новые данные, а не ссылается на существующие. Например, пусть запрос "rUsT": в этом строковом срезе нет строчных u или t, которые мы могли бы использовать, поэтому мы должны выделить новую String с содержимым "rust". Теперь, когда мы передаем query в качестве аргумента методу contains, мы должны добавить амперсанд [3], потому что сигнатура contains определена для приема строкового среза.

Далее, мы добавляем вызов to_lowercase для каждой line, чтобы привести все символы к нижнему регистру [2]. Теперь, когда мы преобразовали line и query в нижний регистр, мы найдем совпадения независимо от регистра запроса.

Посмотрим, проходит ли данная реализация тесты:

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

Отлично! Тесты пройдены. Теперь вызовем новую функцию search_case_insensitive из функции run. Сначала мы добавим конфигурационный параметр в структуру 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, как показано в Listing 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(())
}

Listing 12-22: Вызов функции search или search_case_insensitive в зависимости от значения config.ignore_case

Наконец, нам нужно проверить наличие переменной окружения. Функции для работы с переменными окружения находятся в модуле env стандартной библиотеки, поэтому мы подключаем этот модуль в начале файла src/lib.rs. Затем мы используем функцию var из модуля env, чтобы проверить, установлено ли какое-либо значение для переменной окружения с именем IGNORE_CASE, как показано в Listing 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,
        })
    }
}

Listing 12-23: Проверка наличия значения в переменной окружения с именем IGNORE_CASE

Здесь мы создаем новую переменную ignore_case. Чтобы установить ее значение, мы вызываем функцию env::var и передаем ей имя переменной окружения IGNORE_CASE. Функция env::var возвращает Result, которое будет успешной вариацией Ok, содержащей значение переменной окружения, если переменная окружения установлена в любое значение. Она вернет вариацию Err, если переменная окружения не установлена.

Мы используем метод is_ok на Result, чтобы проверить, установлена ли переменная окружения, что означает, что программа должна выполнять нечувствительный к регистру поиск. Если переменная окружения IGNORE_CASE не установлена в ничего, is_ok вернет false, и программа будет выполнять чувствительный к регистру поиск. Мы не заботимся о значении переменной окружения, только о том, установлена она или нет, поэтому мы проверяем is_ok, а не используем unwrap, expect или любые другие методы, которые мы видели на Result.

Мы передаем значение переменной ignore_case в экземпляр Config, чтобы функция run могла прочитать это значение и решить, вызывать search_case_insensitive или search, как мы реализовали в Listing 12-22.

Попробуем! Сначала мы запустим нашу программу без установки переменной окружения и с запросом 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_CASE в 1, но с тем же запросом to:

IGNORE_CASE=1 cargo run -- to poem.txt

Если вы используете PowerShell, вам нужно будет установить переменную окружения и запустить программу в отдельных командах:

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

Это сделает IGNORE_CASE постоянной на протяжении оставшейся части сеанса оболочки. Его можно удалить с помощью cmdlet 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, чтобы улучшить свои навыки.