Перейти к содержимому

Аргументы командной строки в Rust

Большинству маленьких проектов, которые стоит написать для практики, — списку дел, счётчику слов — нужно получать данные из командной строки, а не держать значения прямо в исходном коде. На этой странице — основы.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}

Если скомпилировать это в программу greet и запустить:

Terminal window
./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.

Вручную обращаться к args по индексам быстро становится неудобно, когда нужны флаги (--verbose), необязательные значения или понятная справка --help. Стандартное решение — крейт clap (на английском). Он добавляется так:

Terminal window
cargo add clap --features derive
use 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 -- ....

Дан 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]);
}
}

Вывод:

Передано аргументов: 2
1: add
2: купить молоко
▶ Попробуй сам

"купить молоко" считается одним аргументом: в настоящей командной строке ты бы написал его в кавычках (cargo run -- add "купить молоко"), и оболочка передала бы его одной строкой. Индекс 0 — по-прежнему имя программы, поэтому цикл начинается с 1.

Дан 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