Реализация функции 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
содержит много других полезных функций для работы с переменными окружения: ознакомьтесь с его документацией, чтобы узнать, что доступно.