Avatar ·

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

Всем привет! Я только начал изучать Rust и столкнулся с, казалось бы, простой задачей — мне нужно передать параметры в мою программу при запуске из командной строки. Например, я хочу написать утилиту для конвертации изображений, которая принимает путь к исходному файлу и путь для сохранения результата. Я ожидал, что в Rust есть что-то вроде `sys.argv` из Python или `process.argv` из Node.js, но не могу найти прямого аналога. Вот что я пробовал: ```rust fn main() { let args = std::env::args(); println!("Arguments: {:?}", args); } ``` Но в консоль выводится что-то странное вроде `Args { inner: [...] }`, а не список строк. Я хочу получить просто массив строк, чтобы потом обращаться к ним по индексу: `args[0]` (имя программы), `args[1]` (первый аргумент) и так далее. Также меня интересует, как правильно обрабатывать ситуацию, когда пользователь не передал нужные аргументы — нужно выводить сообщение об ошибке и инструкцию по использованию. И ещё: есть ли встроенные средства для парсинга более сложных опций, типа `--input file.txt --verbose`? Буду благодарен за примеры кода и объяснения для новичков. Спасибо! <<>>

Основной способ: std::env::args()

В Rust для доступа к аргументам командной строки используется функция std::env::args(). Она возвращает итератор, а не готовый вектор. Чтобы получить коллекцию строк, нужно собрать итератор в Vec<String>:

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    
    // args[0] — имя программы
    // args[1], args[2]... — переданные аргументы
    
    println!("Программа: {}", args[0]);
    
    if args.len() < 2 {
        eprintln!("Ошибка: укажите имя файла");
        eprintln!("Использование: {} <файл>", args[0]);
        std::process::exit(1);
    }
    
    let filename = &args[1];
    println!("Обрабатываю файл: {}", filename);
}

Важные моменты:

  • Первый аргумент (индекс 0) — это всегда путь к исполняемому файлу программы.
  • Аргументы начинаются с индекса 1.
  • Функция args() паникует, если аргументы содержат невалидный Unicode. Если нужна поддержка любых байтов, используйте std::env::args_os(), которая возвращает OsString.
  • Для вывода ошибок используйте eprintln! (stderr), а не println! (stdout).
  • std::process::exit(1) завершает программу с кодом ошибки (ненулевое значение).

Парсинг флагов (--verbose, -v)

Для простых случаев можно разобрать аргументы вручную:

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut input_file = String::new();
    let mut verbose = false;
    let mut output_file = String::new();
    
    let mut i = 1;
    while i < args.len() {
        match args[i].as_str() {
            "-i" | "--input" => {
                i += 1;
                if i < args.len() {
                    input_file = args[i].clone();
                } else {
                    eprintln!("Ошибка: --input требует значение");
                    std::process::exit(1);
                }
            }
            "-o" | "--output" => {
                i += 1;
                if i < args.len() {
                    output_file = args[i].clone();
                } else {
                    eprintln!("Ошибка: --output требует значение");
                    std::process::exit(1);
                }
            }
            "-v" | "--verbose" => verbose = true,
            "-h" | "--help" => {
                println!("Использование: {} [ОПЦИИ]", args[0]);
                println!("  -i, --input <ФАЙЛ>    Входной файл");
                println!("  -o, --output <ФАЙЛ>   Выходной файл");
                println!("  -v, --verbose          Подробный вывод");
                println!("  -h, --help             Эта справка");
                std::process::exit(0);
            }
            _ => {
                eprintln!("Неизвестный аргумент: {}", args[i]);
                std::process::exit(1);
            }
        }
        i += 1;
    }
    
    if verbose {
        println!("Входной файл: {}", input_file);
        println!("Выходной файл: {}", output_file);
    }
}

Продвинутый парсинг с крейтом clap

Для серьёзных проектов лучше использовать готовую библиотеку clap. Добавьте в Cargo.toml:

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

Пример с декларативным парсингом:

use clap::Parser;

#[derive(Parser)]
#[command(name = "image-converter")]
#[command(about = "Конвертирует изображения", long_about = None)]
struct Cli {
    /// Входной файл
    #[arg(short = 'i', long = "input")]
    input: String,

    /// Выходной файл (опционально)
    #[arg(short = 'o', long = "output")]
    output: Option<String>,

    /// Подробный вывод
    #[arg(short = 'v', long = "verbose", default_value_t = false)]
    verbose: bool,
}

fn main() {
    let cli = Cli::parse();
    
    println!("Вход: {}", cli.input);
    if let Some(out) = cli.output {
        println!("Выход: {}", out);
    }
    if cli.verbose {
        println!("Режим подробного вывода включён");
    }
}

clap автоматически генерирует справку (--help), проверяет обязательные аргументы и выводит понятные сообщения об ошибках. Это стандарт де-факто в экосистеме Rust для работы с CLI.

<<>>

Альтернатива: использование std::env::args_os()

Если ваша программа должна работать с не-UTF8 именами файлов (например, в Windows или с файлами в кодировке CP1251), используйте args_os():

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

fn main() {
    let args: Vec<OsString> = env::args_os().collect();
    
    // Преобразование в &str может вернуть None для невалидного UTF-8
    if let Some(program) = args[0].to_str() {
        println!("Программа: {}", program);
    }
    
    // Для вывода на экран лучше использовать отладочный формат
    for (i, arg) in args.iter().enumerate() {
        println!("arg[{}]: {:?}", i, arg);
    }
}

Также обратите внимание на крейт structopt (предшественник clap с derive-макросами) — он всё ещё используется в старых проектах. Современный выбор — clap версии 4.x с feature "derive".

Для минималистичных скриптов, где не нужны флаги, а только позиционные аргументы, можно использовать std::env::args().nth(1):

fn main() {
    let filename = std::env::args().nth(1).unwrap_or_else(|| {
        eprintln!("Usage: program <filename>");
        std::process::exit(1);
    });
    println!("File: {}", filename);
}

Метод nth(1) возвращает Option<String> — первый аргумент после имени программы. Это удобно для быстрых утилит без внешних зависимостей.