Аргументы командной строки в Rust: полный гайд для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как передать и обработать аргументы командной строки в Rust?

Каждый, кто хоть раз писал консольные утилиты на Python, знает, как удобно получать параметры через sys.argv. Но когда вы переходите на Rust — язык системного программирования, который обещает скорость и безопасность, — возникает закономерный вопрос: "How can I access command line parameters in Rust?"

Этот вопрос — один из самых популярных на Stack Overflow (десятки тысяч просмотров), и неспроста. Rust предлагает не один, а целых три основных подхода к работе с аргументами командной строки. От простейшего std::env::args() до мощных библиотек вроде clap и structopt. В этой статье мы разберём каждый из них на практических примерах, чтобы вы могли выбрать подходящий инструмент для своей задачи.

Почему это важно?

Умение работать с аргументами командной строки — базовый навык для любого разработчика на Rust. Без него невозможно создать:

  • CLI-утилиты (например, аналог grep, find или собственный деплой-скрипт);
  • серверные приложения с конфигурацией через флаги запуска;
  • инструменты для автоматизации и DevOps.

Давайте начнём с самого простого способа, а затем плавно перейдём к профессиональным решениям.

Способ 1: std::env::args() — минималистичный подход

Самый простой способ получить аргументы командной строки в Rust — использовать стандартную функцию std::env::args(). Она возвращает итератор по строкам, где первый элемент — это путь к исполняемому файлу, а остальные — переданные параметры.

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    
    // Первый аргумент — это всегда имя программы
    println!("Путь к программе: {}", args[0]);
    
    // Остальные — пользовательские аргументы
    if args.len() > 1 {
        println!("Переданные аргументы:");
        for (i, arg) in args[1..].iter().enumerate() {
            println!("  Аргумент {}: {}", i + 1, arg);
        }
    } else {
        println!("Аргументы не переданы.");
    }
}

Запустим нашу программу:

$ cargo run -- hello world 42
Путь к программе: ./target/debug/myapp
Переданные аргументы:
  Аргумент 1: hello
  Аргумент 2: world
  Аргумент 3: 42

Плюсы: минимальный код, никаких зависимостей, полный контроль.

Минусы: ручная обработка флагов (например, --name или -v), отсутствие проверки типов (все аргументы — строки), нет встроенной справки --help.

Этот способ отлично подходит для быстрых скетчей или когда у вас ровно один обязательный аргумент. Но для серьёзных проектов стоит присмотреться к альтернативам.

Вариант с env::args_os()

Если вам нужны сырые байты (например, для работы с не-UTF8 именами файлов), используйте std::env::args_os(). Она возвращает OsString вместо String:

use std::env;
use std::ffi::OsString;

fn main() {
    let args: Vec<OsString> = env::args_os().collect();
    println!("Первый аргумент (OsString): {:?}", args[0]);
}

Способ 2: Крейт clap — декларативный парсинг

Когда проект разрастается, ручной парсинг аргументов становится источником багов. Тут на помощь приходит clap — самый популярный крейт для работы с командной строкой в экосистеме Rust. Он позволяет декларативно описать все параметры, флаги и подкоманды.

Добавьте в Cargo.toml:

[dependencies]
clap = { version = "4.5", features = ["derive"] }

Теперь пример с использованием derive-макросов (рекомендуемый способ):

use clap::Parser;

/// Простая программа для демонстрации clap
#[derive(Parser)]
#[command(name = "greet")]
#[command(about = "Приветствует пользователя", long_about = None)]
struct Cli {
    /// Имя пользователя
    name: String,

    /// Количество повторений
    #[arg(short = 'c', long = "count", default_value_t = 1)]
    count: u8,

    /// Вывод в верхнем регистре
    #[arg(short = 'u', long = "uppercase")]
    uppercase: bool,
}

fn main() {
    let cli = Cli::parse();

    let greeting = format!("Привет, {}!", cli.name);
    let greeting = if cli.uppercase {
        greeting.to_uppercase()
    } else {
        greeting
    };

    for _ in 0..cli.count {
        println!("{}", greeting);
    }
}

Запустим с разными флагами:

$ cargo run -- Алексей
Привет, Алексей!

$ cargo run -- Алексей -c 3 -u
ПРИВЕТ, АЛЕКСЕЙ!
ПРИВЕТ, АЛЕКСЕЙ!
ПРИВЕТ, АЛЕКСЕЙ!

$ cargo run -- --help
Простая программа для демонстрации clap

Usage: greet [OPTIONS] <NAME>

Arguments:
  <NAME>  Имя пользователя

Options:
  -c, --count <COUNT>      Количество повторений [default: 1]
  -u, --uppercase           Вывод в верхнем регистре
  -h, --help                Print help

Плюсы: автоматическая генерация --help, проверка типов (count — это u8), значения по умолчанию, поддержка подкоманд, валидация.

Минусы: нужно подключать внешнюю зависимость (но clap — это стандарт де-факто, его используют даже в официальных инструментах Rust, таких как cargo).

Способ 3: structopt — элегантный предшественник

Раньше был популярен крейт structopt, но начиная с версии clap 3.x его функционал полностью интегрирован в clap через derive-макросы. Если вы встретите старые туториалы с structopt, знайте: достаточно использовать clap::Parser, как показано выше. Разница минимальна:

// Старый стиль (structopt) — больше не нужен
// #[derive(StructOpt)]
// struct Cli { ... }

// Новый стиль (clap derive)
// #[derive(Parser)]
// struct Cli { ... }

Просто используйте clap — это современный стандарт.

Сравнение подходов: что выбрать?

Чтобы вам было проще определиться, вот краткая таблица:

Критерий std::env::args()

Блоги

Рекомендаций по книгам