Description
Please complete the following tasks
- I have searched the discussions
- I have searched the open and rejected issues
Rust Version
rustc 1.88.0 (6b00bc388 2025-06-23)
Clap Version
4.5.40
Minimal reproducible code
main.rs:
#[allow(dead_code)]
use clap::{Arg, ArgAction, Command};
fn clap_parse() {
let matches: clap::ArgMatches = Command::new("goto")
.about("Jump between known directories")
.author("My Name")
.version(clap::crate_version!())
// Because its not possible with clap to have
// goto [global options] [subcommand] [subcommand options] [positional args]
// (at least from my understanding), use this structure:
// goto [subcommand] [global options + subcommand options] [positional args]
.args_conflicts_with_subcommands(true)
// I disable the auto-supplied stuff here because I want
// to make help and version conflict with any other arguments
// and need to know their ID (= create them) to do that
.disable_help_subcommand(true)
.disable_help_flag(true)
.disable_version_flag(true)
// The user can either use one of these subcommands ...
.subcommand(
Command::new("learn")
.visible_alias("l")
.arg(Arg::new("learn_dirs").num_args(1..).value_name("dir")),
)
.subcommand(
Command::new("forget")
.visible_alias("f")
.arg(Arg::new("forget_dirs").num_args(1..).value_name("dir")),
)
.subcommand(
Command::new("match")
.visible_alias("m")
.arg(Arg::new("match_dirs").num_args(1..).value_name("dir")),
)
.subcommand(
Command::new("edit")
.visible_alias("e")
.arg(Arg::new("edit_dirs").num_args(1..).value_name("dir")),
)
// ... or no subcommand, which tries to change directory to "target".
.arg(
Arg::new("target")
.num_args(1)
.value_name("target")
.conflicts_with_all(["help", "version"])
.help("The query string to jump to"),
)
// These boolean/value flags should work in all cases:
.arg(
Arg::new("cwd")
.global(true)
.num_args(1)
.short('C')
.long("working-dir")
.value_name("dir")
.help("Work from this directory"),
)
.arg(
Arg::new("verbose")
.global(true)
.short('v')
.long("verbose")
.action(ArgAction::SetTrue)
.help("Print extra information"),
)
// Using --version should only be possible like this:
// goto --version <--no trailing stuff here-->
.arg(
Arg::new("version")
.exclusive(true)
.short('V')
.long("version")
.action(ArgAction::Version)
.help("Print the version"),
)
// Using --help should only be possible like this:
// goto --help <--no trailing stuff here-->
// (and if possible also) goto <subcommand> --help <--no trailing stuff here-->
// Although that is a little silly since it would probably be
// ok to display the help anyway. I'd prefer the message
// saying the combination of arguments is illegal still.
.arg(
Arg::new("help")
.exclusive(true)
.short('h')
.long("help")
.action(ArgAction::Help)
.help("Print the version"),
)
.get_matches();
println!("Debug output: {:?}", matches);
if matches.get_flag("verbose") {
println!("verbose mode active");
}
}
fn main() {
clap_parse();
println!("Hello, world!");
}
Steps to reproduce the bug with the above code
- Compile and run the above code to make sure it works
- Run the compiled binary with arguments
cargo run -- foo --help
orcargo run -- foo --version
Actual Behaviour
I observe that these inputs display the help / version just fine even though they should lead to an error based on what the builder code here reads. Something like goto Documents --version
should just be an error, but it seems the target
argument is assigned the string "Documents" and then the ´ArgAction::Version´ runs, even though --version
is supposed to be exclusive.
Expected Behaviour
There should be a runtime error along the lines of error: the argument ... cannot be used with one or more of the other specified arguments
.
Additional Context
I am aware that running help/version terminates early. However I still think this behaviour is confusing and wrong. Replacing the two ArgAction
s with the SetTrue
action, everything just works as intended. But I would very much like to use the automatic help / version display...
Debug Output
No response