clap 4.3: Implementation of `FnOnce` is not general enough
Please complete the following tasks
- [X] I have searched the discussions
- [X] I have searched the open and rejected issues
Rust Version
1.69.0
Clap Version
4.3.0
Minimal reproducible code
use anyhow::Result;
use clap::Parser;
#[derive(Clone, Debug)]
struct Offset {}
impl Offset {
pub fn parse(s: impl AsRef<str>) -> Result<Self> {
Ok(Self {})
}
}
#[derive(Clone, Debug, Parser)]
struct Command {
#[arg(long, value_parser = Offset::parse)]
pub offset: Offset,
}
fn main() {
println!("Hello, world!");
}
Steps to reproduce the bug with the above code
cargo build
Actual Behaviour
error: implementation of `FnOnce` is not general enough
--> src/main.rs:13:24
|
13 | #[derive(Clone, Debug, Parser)]
| ^^^^^^ implementation of `FnOnce` is not general enough
|
= note: `fn(&'2 str) -> Result<Offset, anyhow::Error> {Offset::parse::<&'2 str>}` must implement `FnOnce<(&'1 str,)>`, for any lifetime `'1`...
= note: ...but it actually implements `FnOnce<(&'2 str,)>`, for some specific lifetime `'2`
= note: this error originates in the derive macro `Parser` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> src/main.rs:13:24
|
13 | #[derive(Clone, Debug, Parser)]
| ^^^^^^ one type is more general than the other
|
= note: expected trait `for<'a> Fn<(&'a str,)>`
found trait `Fn<(&str,)>`
note: the lifetime requirement is introduced here
--> /Users/luke/.cargo/registry/src/github.com-1ecc6299db9ec823/clap_builder-4.3.0/src/builder/arg.rs:975:48
|
975 | pub fn value_parser(mut self, parser: impl IntoResettable<super::ValueParser>) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the derive macro `Parser` (in Nightly builds, run with -Z macro-backtrace for more info)
Expected Behaviour
Successful compilation.
Additional Context
This program compiled successfully with clap 4.2, and broke in clap 4.3.
Debug Output
Same as "actual behavior" above.
This program compiled successfully with clap 4.2, and broke in clap 4.3.
I just ran with
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! clap = { version = "=4.2.0", features = ["derive", "debug"] }
//! anyhow = "1"
//! ```
use anyhow::Result;
use clap::Parser;
#[derive(Clone, Debug)]
struct Offset {}
impl Offset {
pub fn parse(s: impl AsRef<str>) -> Result<Self> {
Ok(Self {})
}
}
#[derive(Clone, Debug, Parser)]
struct Command {
#[arg(long, value_parser = Offset::parse)]
pub offset: Offset,
}
fn main() {
println!("Hello, world!");
}
and I'm seeing the failure
even if I drop to
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! clap = { version = "=4.0.0", features = ["derive", "debug"] }
//! anyhow = "1"
//! ```
use anyhow::Result;
use clap::Parser;
#[derive(Clone, Debug)]
struct Offset {}
impl Offset {
pub fn parse(s: impl AsRef<str>) -> Result<Self> {
Ok(Self {})
}
}
#[derive(Clone, Debug, Parser)]
struct Command {
#[arg(long, value_parser = Offset::parse)]
pub offset: Offset,
}
fn main() {
println!("Hello, world!");
}
I see the failure.
Rust Version: 1.74.1 Clap Version: 4.4.11
@epage @lukesneeringer I did a bit of diving into the the problem since I ran into something similar on latest clap & latest rust. I made the smallest reproducible example I could:
Problem
#[derive(Clone)]
struct Offset;
pub fn offset_parse(_: impl AsRef<str>) -> Result<Offset, String> {
Ok(Offset)
}
fn main() {
let arg = clapArg::new("offset").value_parser(offset_parse);
let _cmd = clap::Command::new("command").arg(arg);
}
the compiler (nightly or stable) still seems to have a hard time at showing the actual origin of the problem when dealing with higher ranked trait bounds, the actual source of the error is the line below: https://github.com/clap-rs/clap/blob/d092896d61fd73a5467db85eac035a9ce2ddbc60/clap_builder/src/builder/value_parser.rs#L910
Partial solution
I found a reddit comment that touches very closely on the problem encountered in this issue and managed to reproduce most of the pattern: https://old.reddit.com/r/rust/comments/f1spyt/higher_kinded_lifetime_on_return_types/fh8n2b2/
However, the last step in the post using the nested function that returns an impl still seems to be tricky to invoke:
As it turns out, rustc is quite bad with type inference when it comes to HRTB - for some weird reason here it cannot > prove that our closure is general enough, even though it is. Well, once again, we have to help the compiler:
fn fancy_copy_2(value: i32) -> i32 { fn build_callback(value: i32) -> impl FnOnce(&Source) -> Ref { move |s| { s.new_ref(value) } } call_2(build_callback(value)) }
I've made a separate feature branch to test out the issue that includes the modified impl<F, T, E> TypedValueParser for F:
https://github.com/clap-rs/clap/blob/9920bfaaa7b1488f7106d906c68dfbac74580063/clap_builder/src/builder/value_parser.rs#L908-L935
As well as a unit test to reproduce the issue: https://github.com/clap-rs/clap/blob/9920bfaaa7b1488f7106d906c68dfbac74580063/clap_builder/src/builder/tests.rs#L58-L69
There is still a compilation error but I do believe this is closer to being compiled:
$ cargo test
error: implementation of `Callback` is not general enough
--> clap_builder/src/builder/tests.rs:67:15
|
67 | let arg = Arg::new("offset").value_parser(offset_parse);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Callback` is not general enough
|
= note: `fn(&str) -> std::result::Result<Offset, String> {offset_parse::<&str>}` must implement `Callback<'0>`, for any lifetime `'0`...
= note: ...but it actually implements `Callback<'1>`, for some specific lifetime `'1`
I've experimented with this for a while now and I think this is some limitation in the compiler - I've reported it here, we'll see: https://github.com/rust-lang/rust/issues/119045
I think the best solution for now is to use a closure, i.e.:
let arg = Arg::new("offset").value_parser(|a: &str| offset_parse(a));
EDIT: rust: rustc 1.80.0 (051478957 2024-07-21) clap: 4.5.13
I encountered the similar problem. I'm using clap with builder pattern. Minimal steps to reproduce:
#[derive(Clone)]
pub enum ListColumn {
All,
Url,
Tag,
Desc,
}
// make command and this as arg
.arg(
Arg::new("cols")
.short('c')
.long("cols")
.default_value("all")
.value_parser(|str| {
return if str == "all" {
Ok(ListColumn::All)
} else if str == "tags" {
Ok(ListColumn::Tag)
} else {
Err(clap::Error::new(clap::error::ErrorKind::InvalidValue))
};
})
.help("List the specified cols")
)
// after getting Argmatches
match matches.subcommand() {
// ...
Some(("list", list_task)) => {
let column = list_task.get_one::<ListColumn>("cols").unwrap();
// ...
}
_ => {}
}
And I encountered the error like this:
rustc: implementation of `FnOnce` is not general enough
closure with signature `fn(&'2 str) -> Result<ListColumn, clap::error::Error>` must implement `FnOnce<(&'1 str,)>`, for any lifetime `'1`...
...but it actually implements `FnOnce<(&'2 str,)>`, for some specific lifetime `'2`
rustc: implementation of `Fn` is not general enough
closure with signature `fn(&'2 str) -> Result<ListColumn, clap::error::Error>` must implement `Fn<(&'1 str,)>`, for any lifetime `'1`...
...but it actually implements `Fn<(&'2 str,)>`, for some specific lifetime `'2`
What I'm trying to do is to restrict the user to provide the value only supported by enum ListColumn and thus in value_parser() I've written a small sample logic to accept or throw error based on what is provided by user.
Is there some better way to do this ? Or what I'm doing is correct and this is like a genuine error which needs to be fixed ?
@abhaysp95 I expect a workaround would be to split the closure out into a dedicated function.
Also, using clap::Error might not have the best results. Maybe thats something we can dynamic cast for an improve.
You could also do
PossibleValuesParser::new(["all", "tags"]).map(|s| {
match s {
"all" => ListColumn::All,
"tags" => ListColumn::AllTag,
_ => unimplemented!("PossibleValuesParser prevents getting there")
}
})