🦀

clapでサブコマンドがないときにヘルプを表示しないようにする

に公開

はじめに

これはRustのclapクレートで必須のサブコマンドを指定しなかったときにヘルプメッセージではなくエラーを表示する方法についての記事です。

環境

$ cargo version
cargo 1.86.0 (adf9b6ad1 2025-02-28)
Cargo.toml
[dependencies]
clap = { version = "4.5.38", features = ["derive"] }

方法

以下はaddremoveというサブコマンドを持つコマンドのコードです。
command: Commandは必須のサブコマンドを定義しています。

main.rs
use std::path::PathBuf;

use clap::{Args, Parser, Subcommand};

#[derive(Debug, Parser)]
#[command(version)]
struct Opt {
    #[command(subcommand)]
    command: Command,
}

#[derive(Debug, Subcommand)]
enum Command {
    /// Add file.
    Add(Add),

    /// Remove file.
    Remove(Remove),
}

#[derive(Args, Debug)]
pub struct Add {
    /// File to add.
    file: Option<PathBuf>,
}

#[derive(Args, Debug)]
pub struct Remove {
    /// File to remove.
    file: Option<PathBuf>,
}

fn main() {
    let opt = Opt::parse();
    println!("{opt:?}");
}

このコマンドをサブコマンドを指定しないで実行するとヘルプメッセージが表示され非ゼロで終了します。

$ demo
Usage: demo <COMMAND>

Commands:
  add     Add file
  remove  Remove file
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

このコマンドでサブコマンドを指定していない場合にヘルプメッセージではなくサブコマンドが指定されていない旨のエラーメッセージを表示するには、以下のようにarg_required_else_help(false)を追加します。

main.rs
@@ -3,7 +3,7 @@
 use clap::{Args, Parser, Subcommand};

 #[derive(Debug, Parser)]
-#[command(version)]
+#[command(version, arg_required_else_help(false))]
 struct Opt {
     #[command(subcommand)]
     command: Command,

これにより、ヘルプメッセージではなくエラーメッセージが表示されるようになります。

$ demo
error: 'demo' requires a subcommand but one was not provided
  [subcommands: add, remove, help]

Usage: demo <COMMAND>

For more information, try '--help'.

解説

Derive APIでサブコマンドが必須の場合にサブコマンドを指定しないとヘルプメッセージが表示される理由は、arg_required_else_help(true)が暗黙的に設定されるからです。

https://github.com/clap-rs/clap/blob/6b12a81bafe7b9d013b06981f520ab4c70da5510/CHANGELOG.md?plain=1#L1169

https://github.com/clap-rs/clap/blob/6b12a81bafe7b9d013b06981f520ab4c70da5510/CHANGELOG.md?plain=1#L1609

https://github.com/clap-rs/clap/issues/3280

arg_required_else_helpは引数が存在しない場合にヘルプメッセージを表示して終了するようにするメソッドで、サブコマンドも引数として数えられます。
arg_required_else_help(false)を追加することでこの暗黙的な設定を上書きしています。

サブコマンドが必須の場合はsubcommand_required(true)も暗黙的に設定されます。
subcommand_requiredは実行時にサブコマンドが存在しない場合にエラーメッセージを表示して終了するようにするメソッドです。

サブコマンドがオプションの場合

以下のようにcommand: Commandcommand: Option<Command>に書き換えることでサブコマンドを任意指定にできます。

main.rs
@@ -6,7 +6,7 @@
 #[command(version)]
 struct Opt {
     #[command(subcommand)]
-    command: Command,
+    command: Option<Command>,
 }

 #[derive(Debug, Subcommand)]

この場合はサブコマンドを指定していない場合でも正常に終了します。

$ demo
Opt { command: None }

command: Option<Command>の場合にはarg_required_else_help(true)subcommand_required(true)は暗黙的に設定されません。

他のオプションが存在する場合

以下のようにこのコマンドに--quietというフラグを追加します。

main.rs
@@ -5,6 +5,10 @@
 #[derive(Debug, Parser)]
 #[command(version)]
 struct Opt {
+    /// Suppress warning messages.
+    #[arg(short, long)]
+    quiet: bool,
+
     #[command(subcommand)]
     command: Command,
 }

arg_required_else_help(false)を追加せず、サブコマンドは指定しないでフラグを指定しない場合と指定した場合の結果は以下のようになります。

$ demo
Usage: demo [OPTIONS] <COMMAND>

Commands:
  add     Add file
  remove  Remove file
  help    Print this message or the help of the given subcommand(s)

Options:
  -q, --quiet    Suppress warning messages
  -h, --help     Print help
  -V, --version  Print version
$ demo -q
error: 'demo' requires a subcommand but one was not provided
  [subcommands: add, remove, help]

Usage: demo [OPTIONS] <COMMAND>

For more information, try '--help'.

--quietを指定した場合、引数が存在することからヘルプメッセージではなくエラーメッセージが表示されます。

arg_required_else_help(false)を追加して、サブコマンドは指定しないでフラグを指定しない場合と指定した場合の結果は以下のようになります。

$ demo
error: 'demo' requires a subcommand but one was not provided
  [subcommands: add, remove, help]

Usage: demo [OPTIONS] <COMMAND>

For more information, try '--help'.
$ demo -q
error: 'demo' requires a subcommand but one was not provided
  [subcommands: add, remove, help]

Usage: demo [OPTIONS] <COMMAND>

For more information, try '--help'.

終わりに

個人的には必須のサブコマンドが指定されていない場合はヘルプメッセージではなくエラーメッセージを表示するほうが何が間違っていたのかわかりやすいと思うので、この方法を知ることができて良かったです。

GitHubで編集を提案

Discussion