✏️

RustのClapクレートがメチャクチャ良かった話

2022/05/31に公開4

私事

プライベートでRustを勉強しているのですが、
エンジニアとしてもRustasianとしてもまだまだ未熟であるため、技術評論社から出ている実践Rust入門[言語仕様から開発手法まで]をここ数ヶ月ほど写経していました

https://gihyo.jp/book/2019/978-4-297-10559-4

自分は買うまで気付かなかったんですが(笑)、こちらの書籍が2~3年前のものになります
当然ですが、中で使わているクレートなどが色々とアップデートされていて、
書き換えながら読み進めている感じです
新参者にはなかなか厳しい戦いです😫
しかしながら、書籍は良書で、すごく勉強になってます!

そんな中でも、
この書籍で紹介されているClapというクレートが、個人的にはいい意味でバージョンアップしていたので、自分と同じようなことをしている人のために書籍版とバージョンアップで書き換えた版で比較を載せておこうと思います

そもそもClapについてですが

RustでCLIパーサーを簡単に書くためのクレートです
https://github.com/clap-rs/clap

書籍では、11章『Webアプリケーション, データベース接続』で登場します

このClapのv3から新しくDerive APIというのがサポートされるようになったようで、
それがRustのDeriveをフル活用して、元々のBuilder APIを使うよりシンプルに書けたので、個人的にかなり気に入りました

実際のコード

こちらは書籍で公開されているサンプルコードになります

https://github.com/ghmagazine/rustbook/blob/4dfc6ca40c5c9b9d690606915a12b8aa8430d35c/ch11/log-collector/cli/src/main.rs#L69-L124

そして、こちらの内容がDerive APIで書き換えたものになります

Derive APIを使用
use clap::{Parser, Subcommand, ArgEnum};
use reqwest::{blocking};
use std::io;

#[derive(Debug, Parser)]
#[clap(
    name = env!("CARGO_PKG_NAME"),
    version = env!("CARGO_PKG_VERSION"),
    author = env!("CARGO_PKG_AUTHORS"),
    about = env!("CARGO_PKG_DESCRIPTION"),
    arg_required_else_help = true,
)]
struct Cli {
    #[clap(subcommand)]
    subcommand: SubCommands,
    /// server url
    #[clap(short = 's', long = "server", value_name = "URL", default_value = "localhost:3000")]
    server: String,
}

#[derive(Debug, Subcommand)]
enum SubCommands {
    /// get logs
    #[clap(arg_required_else_help = true)]
    Get {
        /// log format
        #[clap(
            short = 'f',
            long = "format",
            required = true,
            ignore_case = true,
            arg_enum,
        )]
        format: Format,
    },
    /// post logs, taking input from stdin
    Post,
}

#[derive(Debug, Clone, ArgEnum)]
enum Format {
    Csv,
    Json,
}

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

    let client = blocking::Client::new();
    let api_client = ApiClient { server: cli.server, client };

    match cli.subcommand {
        SubCommands::Get { format } => {
            match format {
                Format::Csv => unimplemented!(),
                Format::Json => do_get_json(&api_client)
            }
        },
        SubCommands::Post => do_post_csv(&api_client)
    }
}

記述量はそんなに変わりませんが、
メソッドチェーンで書いていたオプション(.short, .long)やコマンドの説明(.about)などが移動して、main関数の中はかなりシンプルになったかと思います

これだけで以下のようなきれいなヘルプ表示を確認することができます

実行結果
cli 0.1.0
CLI for web server requests.

USAGE:
    cli [OPTIONS] <SUBCOMMAND>

OPTIONS:
    -h, --help            Print help information
    -s, --server <URL>    server url [default: localhost:3000]
    -V, --version         Print version information

SUBCOMMANDS:
    get     get logs
    help    Print this message or the help of the given subcommand(s)
    post    post logs, taking input from stdin

オプションやサブコマンドを受け取るためのStructEnumが新たに増え、
これらに対して#[derive]アトリビュートと#[clap]アトリビュートでコマンドやオプションを設定していきます

アトリビュートについて(ざっくり)

#[derive]#[clap]を対で書いていく感じです
structのフィールドに#[clap(subcommand)]でサブコマンドであることを明示したら、
サブコマンドとして扱うstructには#[derive(Subcommand)]でサブコマンドとしての実装をするように明記する

#[derive(Parser)]

Clapでパーサーを作成するための大元になります
~::parse()でオブジェクトを作成したらオプションに当たるstructなどを呼び出すことができます

#[clap(name = env!("CARGO_PKG_NAME"),version = env!("CARGO_PKG_VERSION"),author = env!("CARGO_PKG_AUTHORS"),about = env!("CARGO_PKG_DESCRIPTION"),arg_required_else_help = true)]

CLIのhelpなどに作成者やCLIのバージョンなどを表示してくれます
env!("CARGO_PKG_~")Cargo.tomlから環境変数を呼び出しています

#[clap(short = 's', long = "server", value_name = "URL", default_value = "localhost:3000")]

オプションについて記述するものになります
shortは省略時、longは非省略時のオプションになります
value_nameはどういった値を書くかを示します

#[clap(subcommand)]#[derive(Subcommand)]

サブコマンドについて記述するものになります

#[clap(arg_enum)]#[derive(ArgEnum)]

列挙要素に関して記述するものになります

抜粋
OPTIONS:
    -f, --format <FORMAT>    log format [possible values: csv, json]

上記のようにヘルプ表示で分かる通り、入力できるパラメータなどについて制限できます

あれ?ヘルプ表示のコメントはどこへ?

と思った方もいるかも知れません
自分は公式のExampleを見た時、2,3回コードと表示例を見比べました

なんと、
このDerive APIはフィールドに書いたコメントをそのままヘルプ表示に表示してくれます(すごい!!)
cargo docもそうですが、Rustのこういう所すごく好きです

最後に

いかがでしたでしょうか?
自分の理解力不足で間違っていることや説明不足なところ、大いにあると思います
その時はコメント頂ければと思います

ここで説明してないことなどに関しても、
公式のExampleがとにかく充実しているので、Exampleを見れば大体のことが分かると思います

(自分は現在、書籍に関しては一通り読み終わったのですが、
actixが提供するactix-multipartを使ってのCSVアップロードをどうするのかで詰まっているので、そのへんもご教授いただけると助かります🙇)

Rustasianの人口が増えることと自社でRustのプロダクトが発足される日を切に願ってます😆

Discussion

tatsuya6502tatsuya6502

こんにちは。『実践Rust入門』の共著者の一人です。前半を執筆しました。

書籍の方をじっくり読んでいただけたようで、ありがとうございます 😊

(自分は現在、書籍に関しては一通り読み終わったのですが、
actixが提供するactix-multipartを使ってのCSVアップロードをどうするのかで詰まっているので、そのへんもご教授いただけると助かります🙇)

『はじめに』の最後の節『困ったときは』のところに書いたのですが、Slackに『rust-jp』という日本語コミュニティーがあります。もしよかったら、そちらで質問してみてください。 #actix-webというチャネル(channel)がありますので、そこで質問するのが良いでしょう。

ここから登録できます。 http://rust-jp.herokuapp.com

(すでにご登録済みでしたら無視してください)

私自身はWeb系は詳しくないのですが、#actix-webにはActix-Webを業務で使われている方も何人かいらっしゃるようです。actix-multipartについても何か情報が得られるかもしれません。(書籍の後半を執筆されたκeenさんもいらっしゃいます)

Sh!n0buSh!n0bu

著者であるtatsuya6502さんにコメントいただけるとはありがとうございます!
励みになります🙏

Slackに『rust-jp』という日本語コミュニティーがあります。

Slackのチャンネルには入っているのですが、
質問したことや#actix-webチャンネルがあることは知らなかったので、
質問してみようと思います

アウトプットを意識して始めた記事ですが、
Rustコミュニティの活性化のために、今後も何かあれば書いていこうと思います