Аргументы командной строки в Rust
Большинству маленьких проектов, которые стоит написать для практики, — списку дел, счётчику слов — нужно получать данные из командной строки, а не держать значения прямо в исходном коде. На этой странице — основы.
Читаем аргументы через std::env::args
Заголовок раздела «Читаем аргументы через std::env::args»use std::env;
fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args);}Если скомпилировать это в программу greet и запустить:
./greet Алисаты увидишь:
["greet", "Алиса"]Первый элемент — всегда имя самой программы. Аргументы, которые ты на самом деле ввёл, начинаются с индекса 1.
Читаем конкретный аргумент
Заголовок раздела «Читаем конкретный аргумент»use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("Использование: greet <имя>"); return; }
let name = &args[1]; println!("Привет, {name}!");}Запусти через cargo run -- Алиса и получишь:
Привет, Алиса!Запусти без аргументов — и вместо падения увидишь подсказку по использованию. Проверка args.len() перед обращением по индексу спасает от паники, если кто-то забудет что-нибудь передать.
Превращаем аргумент в число
Заголовок раздела «Превращаем аргумент в число»Каждый аргумент приходит как String, поэтому часто нужен .parse() (со страницы Приведение типов), чтобы превратить его в число:
use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("Использование: square <число>"); return; }
match args[1].parse::<i32>() { Ok(n) => println!("{}", n * n), Err(_) => println!("Это не число"), }}Обрабатываем простой флаг вручную
Заголовок раздела «Обрабатываем простой флаг вручную»Флаг — это аргумент, который что-то включает, например --shout. Прежде чем брать крейт, полезно посмотреть, как обработать флаг самому. Проверять это легко, если вынести логику в функцию, которая принимает аргументы как срез &[String]. Тогда main может передать настоящие аргументы, а ты во время экспериментов — список, собранный вручную:
fn run(args: &[String]) { let mut shout = false; let mut names = Vec::new();
// пропускаем индекс 0 — имя программы for arg in &args[1..] { if arg == "--shout" { shout = true; } else { names.push(arg); } }
if names.is_empty() { println!("Использование: greet [--shout] <имя>..."); return; }
for name in names { let line = format!("Привет, {name}!"); if shout { println!("{}", line.to_uppercase()); } else { println!("{line}"); } }}
fn main() { // В настоящей программе ты бы написал: // let args: Vec<String> = std::env::args().collect(); // Здесь мы собираем список вручную, чтобы это работало и в Playground. let args = vec![ String::from("greet"), String::from("--shout"), String::from("Аня"), String::from("Бен"), ]; run(&args);}Вывод:
ПРИВЕТ, АНЯ!ПРИВЕТ, БЕН!На что стоит обратить внимание:
&args[1..]— это все аргументы, кроме имени программы. Флаг может стоять где угодно:greet Аня --shout Бентоже работает.- Убери
--shoutиз списка и запусти снова — получишь обычные приветствия. Убери ещё и имена — увидишь подсказку по использованию. runвсё равно, откуда пришёл список, поэтому переход наstd::env::args().collect()— это изменение одной строки вmain.to_uppercaseпонимает Unicode, так что кириллица тоже становится заглавной.
Для одного-двух флагов этого достаточно. Но как только понадобятся короткие формы (-s), значения после флага (--count 3) и справка --help, самописный цикл быстро разрастается. Тут самое время перейти на clap.
Если аргументов больше пары — бери clap
Заголовок раздела «Если аргументов больше пары — бери clap»Вручную обращаться к args по индексам быстро становится неудобно, когда нужны флаги (--verbose), необязательные значения или понятная справка --help. Стандартное решение — крейт clap (на английском). Он добавляется так:
cargo add clap --features deriveuse clap::Parser;
#[derive(Parser)]struct Args { name: String,
#[arg(short, long, default_value_t = 1)] count: u32,}
fn main() { let args = Args::parse(); for _ in 0..args.count { println!("Hello, {}!", args.name); }}Так ты получаешь разбор аргументов, их проверку и работающую справку --help — всё это генерируется из определения структуры. Для быстрых скриптов clap не нужен, но для всего, чем ты будешь пользоваться больше одного раза, он экономит кучу ручной работы.
Практика
Заголовок раздела «Практика»Playground не умеет передавать настоящие аргументы, поэтому оба упражнения начинаются с Vec<String>, собранного вручную. Он выглядит точно так же, как то, что вернул бы env::args().collect(). На своём компьютере замени его на настоящий и попробуй cargo run -- ....
1. Перечисляем аргументы
Заголовок раздела «1. Перечисляем аргументы»Дан vec!["notes", "add", "купить молоко"] (в виде String). Выведи, сколько аргументов ввёл пользователь (не считая имени программы), а затем каждый аргумент с его номером, начиная с 1.
Подсказка: количество — это args.len() - 1. Цикл for i in 1..args.len() даёт каждый индекс после имени программы.
Показать решение
fn main() { // представим, что это пришло из std::env::args().collect() let args = vec![ String::from("notes"), String::from("add"), String::from("купить молоко"), ];
println!("Передано аргументов: {}", args.len() - 1); for i in 1..args.len() { println!("{i}: {}", args[i]); }}Вывод:
Передано аргументов: 21: add2: купить молоко"купить молоко" считается одним аргументом: в настоящей командной строке ты бы написал его в кавычках (cargo run -- add "купить молоко"), и оболочка передала бы его одной строкой. Индекс 0 — по-прежнему имя программы, поэтому цикл начинается с 1.
2. Складываем числа
Заголовок раздела «2. Складываем числа»Дан vec!["sum", "4", "10", "x", "6"]. Сложи все аргументы, которые являются числами, а для остальных выведи предупреждение. В конце выведи сумму.
Подсказка: перебирай &args[1..] и используй match arg.parse::<i32>(), как в разделе Превращаем аргумент в число. Заведи let mut total = 0; перед циклом.
Показать решение
fn main() { let args = vec![ String::from("sum"), String::from("4"), String::from("10"), String::from("x"), String::from("6"), ];
let mut total = 0; for arg in &args[1..] { match arg.parse::<i32>() { Ok(n) => total += n, Err(_) => println!("Пропускаю '{arg}': это не число"), } } println!("Сумма: {total}");}Вывод:
Пропускаю 'x': это не числоСумма: 20Каждый аргумент приходит как текст, даже "4", поэтому каждый нужно разобрать. Пропустить плохой ввод с сообщением дружелюбнее, чем упасть. Если твоей программе так удобнее, можно и остановиться на первом плохом аргументе через return.
std::env::args().collect()даётVec<String>с аргументами командной строки; индекс 0 — всегда имя программы.- Проверяй
args.len()перед обращением по индексу, чтобы пропущенный аргумент не вызвал панику. .parse()превращает аргумент-Stringв число, как и везде.- Вынеси обработку аргументов в функцию, которая принимает
&[String], — тогда её можно проверить на списке, собранном вручную. - Если аргументов больше пары простых, стандартный и проверенный выбор — крейт
clap.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что лежит в первом элементе Vec, который вернул env::args().collect()?
2. Зачем проверять args.len() перед обращением к args по индексу?
3. Что даёт крейт clap по сравнению с ручным доступом к args?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- Получение аргументов командной строки — книга «Язык программирования Rust»: начало проекта
minigrepиз книги, который читает из командной строки слово для поиска и имя файла (перевод на русский). std::env::args— документация стандартной библиотеки (на английском): что именно возвращаетargs()и когда он паникует (аргументы, которые не являются корректным Unicode).- Аргументы программы — Rust на примерах: короткий пример и продолжение про разбор аргументов через
match(перевод на русский). - Command Line Applications in Rust (на английском): бесплатная мини-книга о создании настоящих CLI-инструментов — от разбора аргументов до тестирования и распространения.
- clap derive tutorial — docs.rs (на английском): пошаговое руководство по флагам, опциям, подкомандам и проверке значений с
clap.