hyunsu blog

홈강좌Rust 실무 16편 중 6편째

Rust clap — 구조체 하나로 명령줄 옵션과 --help 를 만든다

목차 · 4
  1. 01구조체가 곧 옵션 정의다
  2. 02--help 는 공짜다
  3. 03실행과 잘못된 값
  4. 04실습 — 옵션 하나 더하기

clap 은 Rust 명령줄 도구의 옵션을 읽는 크레이트다. 구조체에 #[derive(Parser)] 를 붙이면 칸이 인자·옵션이 되고, 문서 주석이 --help 문장이 된다. 잘못된 값은 clap 이 막고 종료 코드 2 로 끝낸다. 입문 34·47편의 wordfreq 에 --top 을 달아 본다.

01 구조체가 곧 옵션 정의다

derive 기능을 켜고 붙인다.

toml
[package]
name = "wordfreq"
version = "0.3.0"
edition = "2024"

[dependencies]
clap = { version = "=4.6.7", features = ["derive"] }
rust
use clap::Parser;
use std::collections::HashMap;
use std::fs;

/// Count the most frequent words in a text file.
#[derive(Parser)]
#[command(version)]
struct Args {
    /// File to read
    file: String,

    /// How many words to show
    #[arg(short, long, default_value_t = 10)]
    top: usize,

    /// Keep upper and lower case apart
    #[arg(long)]
    case_sensitive: bool,
}

fn main() {
    let args = Args::parse();
    let text = fs::read_to_string(&args.file).unwrap_or_default();

    let mut counts: HashMap<String, usize> = HashMap::new();
    for raw in text.split_whitespace() {
        let word = raw.trim_matches(['.', ',', '!', '?']);
        let word = if args.case_sensitive { word.to_string() } else { word.to_lowercase() };
        *counts.entry(word).or_insert(0) += 1;
    }

    let mut pairs: Vec<_> = counts.into_iter().collect();
    pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
    for (word, n) in pairs.into_iter().take(args.top) {
        println!("{n:>3} {word}");
    }
}
  • #[derive(Parser)] 를 붙인 구조체의 칸 하나하나가 명령줄 항목이다. Args::parse() 가 실제 인자를 읽어 구조체를 채운다. 입문 34편처럼 env::args() 를 손으로 뒤지지 않는다.
  • 이름표 없는 칸(file)은 위치로 받는 인자다. 반드시 줘야 한다.
  • #[arg(short, long)] 은 -t 와 --top 두 이름으로 받는다. default_value_t = 10 은 안 주면 10 이다. 타입이 usize 라서 숫자로 바꾸는 것까지 clap 이 한다.
  • 타입이 bool 인 칸은 값을 받지 않는 스위치다. --case-sensitive 를 쓰면 true 다.
  • /// 로 쓴 문서 주석이 도움말 문장이 된다.

02 --help 는 공짜다

bash
cargo run -- --help
text
Count the most frequent words in a text file

Usage: wordfreq [OPTIONS] <FILE>

Arguments:
  <FILE>  File to read

Options:
  -t, --top <TOP>       How many words to show [default: 10]
      --case-sensitive  Keep upper and lower case apart
  -h, --help            Print help
  -V, --version         Print version

구조체와 주석만 썼는데 사용법·인자·옵션·기본값이 정리된 도움말이 나왔다. --version 은 #[command(version)] 이 Cargo.toml 의 버전으로 채운다. 옵션을 더하면 도움말도 저절로 바뀌니, 코드와 도움말이 어긋날 일이 없다.

03 실행과 잘못된 값

bash
cargo run -- sample.txt --top 3
text
     Running `target/debug/wordfreq sample.txt --top 3`
  8 the
  5 is
  4 and

입문 34편의 sample.txt 에서 상위 3개만 나왔다. 숫자 자리에 글자를 넣으면 어떻게 될까.

bash
cargo run -- sample.txt --top three
text
     Running `target/debug/wordfreq sample.txt --top three`
error: invalid value 'three' for '--top <TOP>': invalid digit found in string

For more information, try '--help'.

우리 코드는 한 줄도 돌지 않았다. clap 이 'three' 가 --top 에 맞지 않는 값이라고(invalid value) 막고, 이유(숫자가 아닌 글자)와 --help 안내를 찍었다. 이때 종료 코드는 2 다. 명령줄 도구에서 2 는 "사용법이 틀렸다" 는 관례적인 값이라, 입문 34편에서 손으로 process::exit(2) 하던 일을 clap 이 대신 해 준다.

04 실습 — 옵션 하나 더하기

최소 글자 수보다 짧은 단어를 빼는 --min-len 옵션을 더해 보자.

  1. Args 에 #[arg(long, default_value_t = 1)] min_len: usize 를 더한다.
  2. 세는 반복에서 word.chars().count() < args.min_len 이면 continue 한다(입문 21편, 한글은 chars() 로 센다).
  3. cargo run -- --help 로 도움말에 저절로 들어갔는지 본다.

명령줄 도구의 다음 단계는 하위 명령(git commit 처럼)이다. clap 은 enum 에 #[derive(Subcommand)] 를 붙여 만든다. 다음 편은 웹 API 서버를 만드는 axum 이다.