Clap для Rust: полное руководство с примерами

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

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

Начать курс

Что такое Clap и зачем он нужен?

Clap (Command Line Argument Parser) — это мощная, быстрая и удобная библиотека для парсинга аргументов командной строки в языке Rust. Она позволяет разработчикам легко определять, какие аргументы, флаги, опции и подкоманды должно принимать их приложение, автоматически генерировать справку (--help), проверять типы данных и обрабатывать ошибки ввода.

Зачем нужен Clap? Вместо того чтобы вручную разбирать std::env::args() и писать десятки строк кода для валидации, Clap делает всю работу за вас. Вы просто описываете структуру вашего CLI, а библиотека берёт на себя парсинг, генерацию сообщений об ошибках и форматирование подсказок. Clap поддерживает:

  • Позиционные аргументы (обязательные и опциональные).
  • Именованные опции (флаги) с короткими и длинными именами.
  • Подкоманды (как в git commit).
  • Автодополнение для оболочек (bash, zsh, fish, PowerShell).
  • Валидацию значений, значения по умолчанию, множественные значения и многое другое.

Clap — это стандарт де-факто для создания CLI-приложений в экосистеме Rust. Он используется в таких известных проектах, как cargo, ripgrep (rg), fd и многих других.

Установка

Clap — это библиотека для Rust, поэтому она устанавливается через менеджер пакетов cargo. Добавьте следующую строку в ваш файл Cargo.toml:

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

Мы рекомендуем использовать derive-макросы (фича derive), так как это самый современный и удобный способ работы с Clap. Если вам нужен только базовый функционал без derive, можно написать:

[dependencies]
clap = "4.5"

После добавления зависимости выполните команду cargo build, чтобы загрузить и скомпилировать библиотеку.

Быстрый старт с примером кода

Давайте создадим простое приложение, которое принимает имя пользователя и опциональный флаг приветствия. Создайте новый проект:

cargo new hello_clap
cd hello_clap

Замените содержимое src/main.rs на следующий код:

use clap::Parser;

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

    /// Количество раз, которое нужно повторить приветствие
    #[arg(short = 'c', long = "count", default_value_t = 1)]
    count: u8,
}

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

    for _ in 0..cli.count {
        println!("Привет, {}!", cli.name);
    }
}

Теперь запустите программу:

cargo run -- Мир
# Вывод: Привет, Мир!

cargo run -- Мир -c 3
# Вывод:
# Привет, Мир!
# Привет, Мир!
# Привет, Мир!

cargo run -- --help
# Вывод справки

Как видите, Clap автоматически сгенерировал справку, обработал аргументы и выполнил проверку типов (если передать -c не число, будет ошибка).

Основные методы и функции Clap

Рассмотрим ключевые методы и атрибуты, которые предоставляет Clap. Мы будем использовать подход с derive-макросами, так как он наиболее популярен.

1. #[derive(Parser)]

Что делает: Превращает вашу структуру в парсер командной строки. Это основной макрос, с которого всё начинается. Он автоматически реализует трейт Parser для вашей структуры.

Параметры: Атрибуты структуры (#[command(...)]) и полей (#[arg(...)]).

Пример:

use clap::Parser;

#[derive(Parser)]
#[command(name = "myapp")]
struct Cli {
    #[arg(short, long)]
    verbose: bool,
}

2. Cli::parse()

Что делает: Запускает процесс парсинга аргументов командной строки (из std::env::args()). Если парсинг не удался (например, не хватает обязательного аргумента или передан неверный тип), программа завершится с сообщением об ошибке и подсказкой.

Параметры: Нет (использует глобальные аргументы).

Пример:

fn main() {
    let cli = Cli::parse();
    // ... используем cli
}

3. #[arg(short, long)]

Что делает: Атрибут для поля структуры. Делает поле опцией командной строки с коротким (-s) и/или длинным (--long) флагом. Имя флага автоматически генерируется из имени поля (например, поле verbose станет --verbose и -v).

Параметры: short (символ или true для авто), long (строка или true для авто), default_value_t, required, help и другие.

Пример:

struct Cli {
    #[arg(short = 'o', long = "output", default_value = "out.txt")]
    output_file: String,

    #[arg(short, long)]
    debug: bool,
}

4. #[arg(default_value_t = ...)]

Что делает: Устанавливает значение по умолчанию для опции, если она не была указана пользователем. default_value_t принимает литерал или выражение, которое вычисляется во время компиляции.

Параметры: Значение того же типа, что и поле.

Пример:

struct Cli {
    #[arg(short, long, default_value_t = 8080)]
    port: u16,
}

5. #[arg(required = true)]

Что делает: Делает опцию обязательной. Если пользователь не укажет её, Clap выдаст ошибку. Для позиционных аргументов (без short/long) это поведение по умолчанию.

Параметры: true или false.

Пример:

struct Cli {
    #[arg(short, long, required = true)]
    config: String,
}

6. #[arg(value_parser = ...)]

Что делает: Позволяет задать кастомный парсер для значения. Это может быть встроенный парсер (например, для перечислений) или ваша собственная функция. Очень полезно для валидации.

Параметры: Функция или замыкание, которое принимает &str и возвращает Result<T, String>.

Пример:

fn parse_size(s: &str) -> Result<u64, String> {
    let s = s.trim().to_lowercase();
    if let Some(num) = s.strip_suffix("kb") {
        num.parse::<u64>().map(|n| n * 1024).map_err(|e| e.to_string())
    } else if let Some(num) = s.strip_suffix("mb") {
        num.parse::<u64>().map(|n| n * 1024 * 1024).map_err(|e| e.to_string())
    } else {
        s.parse::<u64>().map_err(|_| "Invalid size".to_string())
    }
}

struct Cli {
    #[arg(short, long, value_parser = parse_size)]
    size: u64,
}

7. #[command(subcommand)]

Что делает: Определяет вложенную команду (подкоманду). Поле должно быть перечислением (enum), которое также реализует Subcommand. Позволяет создавать сложные CLI, как у Git или Docker.

Параметры: Атрибут для поля структуры.

Пример:

use clap::{Parser, Subcommand};

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Добавить элемент
    Add { name: String },
    /// Удалить элемент
    Remove { id: u32 },
    /// Список всех элементов
    List,
}

fn main() {
    let cli = Cli::parse();
    match cli.command {
        Commands::Add { name } => println!("Добавлено: {}", name),
        Commands::Remove { id } => println!("Удалено: {}", id),
        Commands::List => println!("Список..."),
    }
}

8. #[arg(short, long, action = clap::ArgAction::Count)]

Что делает: Используется для подсчёта количества повторений флага. Например, -vvv установит значение 3. Тип поля должен быть u8 или другой целочисленный.

Параметры: clap::ArgAction::Count.

Пример:

struct Cli {
    #[arg(short, long, action = clap::ArgAction::Count)]
    verbose: u8,
}

fn main() {
    let cli = Cli::parse();
    println!("Уровень детализации: {}", cli.verbose);
}

9. #[arg(num_args = 1..)]

Что делает: Позволяет указать, сколько значений может принимать опция. Можно задать диапазон, например, 1..=3 (от 1 до 3) или .. (любое количество). Поле должно быть коллекцией (Vec<T>).

Параметры: Диапазон или число.

Пример:

struct Cli {
    #[arg(short, long, num_args = 1..)]
    files: Vec<String>,
}

// Использование: program --files a.txt b.txt c.txt

10. #[arg(env = "ENV_VAR_NAME")]

Что делает: Позволяет получать значение аргумента из переменной окружения, если он не был указан в командной строке. Это удобно для конфиденциальных данных (пароли, токены).

Параметры: Имя переменной окружения.

Пример:

struct Cli {
    #[arg(short, long, env = "DATABASE_URL")]
    database_url: String,
}

// Можно передать: program --database-url postgres://...
// Или установить переменную: DATABASE_URL=postgres://... program

Реальный практический пример использования

Давайте создадим утилиту для работы с задачами (task manager). Она будет поддерживать добавление, удаление, отметку выполнения и список задач. Данные будем хранить в простом JSON-файле.

Полный код (src/main.rs):

use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize)]
struct Task {
    id: u32,
    description: String,
    done: bool,
}

#[derive(Debug, Serialize, Deserialize)]
struct TaskList {
    tasks: Vec<Task>,
    next_id: u32,                    

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