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. Назовём его minigrep, чтобы не путать с утилитой grep, которая, возможно, уже есть в вашей системе:

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

Первая задача — научить minigrep принимать два аргумента командной строки: путь к файлу и строку для поиска. То есть мы хотим запускать программу через cargo run, затем два дефиса (они говорят, что следующие аргументы — для нашей программы, а не для cargo), строку для поиска и путь к файлу, в котором искать:

$ cargo run -- searchstring example-filename.txt

Сейчас программа, сгенерированная cargo new, не умеет обрабатывать переданные ей аргументы. На crates.io есть библиотеки, которые помогают писать программы с аргументами командной строки, но раз вы только изучаете эту тему, реализуем всё сами.

Чтение значений аргументов

Чтобы minigrep мог читать переданные ему аргументы командной строки, понадобится функция std::env::args из стандартной библиотеки Rust. Она возвращает итератор по аргументам командной строки, переданным minigrep. Подробно итераторы мы разберём в главе 13. А пока достаточно знать о них две вещи: итератор выдаёт последовательность значений, и у итератора можно вызвать метод collect, чтобы превратить его в коллекцию — например, в вектор, — содержащую все выданные им элементы.

Код из листинга 12-1 позволяет программе minigrep прочитать все переданные ей аргументы командной строки и собрать их в вектор.

Файл: src/main.rs
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    dbg!(args);
}
Листинг 12-1: Собираем аргументы командной строки в вектор и выводим их

Сначала инструкцией use подключаем модуль std::env, чтобы пользоваться его функцией args. Заметьте, что функция std::env::args вложена в два уровня модулей. Как мы обсуждали в главе 7, если нужная функция вложена больше чем в один модуль, мы подключаем родительский модуль, а не саму функцию. Так можно легко пользоваться и другими функциями из std::env. К тому же это однозначнее, чем написать use std::env::args и вызывать функцию просто как args: args легко принять за функцию, определённую в текущем модуле.

Функция args и некорректный Юникод

Учтите, что std::env::args запаникует, если какой-то аргумент содержит некорректный Юникод. Если программе нужно принимать такие аргументы, используйте std::env::args_os. Эта функция возвращает итератор, который выдаёт значения OsString вместо String. Здесь мы для простоты взяли std::env::args: значения OsString на разных платформах устроены по-разному, и работать с ними сложнее, чем со String.

В первой строке main мы вызываем env::args и сразу же с помощью collect превращаем итератор в вектор со всеми значениями, которые он выдаёт. Функция collect умеет создавать коллекции самых разных видов, поэтому мы явно указываем тип args: нам нужен вектор строк. В Rust аннотации типов нужны очень редко, но collect — как раз одна из функций, где они часто нужны: Rust не может сам вывести, какая коллекция вам нужна.

Наконец, выводим вектор отладочным макросом. Запустим код сначала без аргументов, а потом с двумя аргументами:

$ 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: программа может узнать, под каким именем её запустили. Это часто удобно: например, чтобы выводить имя программы в сообщениях или менять поведение программы в зависимости от того, через какой псевдоним командной строки её вызвали. Но в этой главе оно нам не нужно, так что мы его пропустим и сохраним только два нужных аргумента.

Сохраняем значения аргументов в переменные

Программа уже умеет получать значения, переданные как аргументы командной строки. Теперь нужно сохранить значения двух аргументов в переменные, чтобы пользоваться ими в остальной программе. Это сделано в листинге 12-2.

Файл: src/main.rs
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}");
}
Листинг 12-2: Создаём переменные для аргумента с запросом и аргумента с путём к файлу

Как мы видели при выводе вектора, имя программы занимает первый элемент вектора, args[0], поэтому аргументы начинаются с индекса 1. Первый аргумент minigrep — строка для поиска, и ссылку на него мы кладём в переменную query (запрос). Второй аргумент — путь к файлу, и ссылку на него мы кладём в переменную file_path.

Мы временно выводим значения этих переменных, чтобы убедиться, что код работает как задумано. Запустим программу снова с аргументами test и sample.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

Отлично, программа работает! Значения нужных аргументов сохраняются в правильные переменные. Позже мы добавим обработку ошибок для некоторых нештатных ситуаций — например, когда пользователь не передал ни одного аргумента. А пока не будем об этом думать и займёмся чтением файла.