Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected behaviour of default_value_t when From<&str> impl isn't the inverse of Display impl #4599

Closed
2 tasks done
cyqsimon opened this issue Jan 2, 2023 · 1 comment
Closed
2 tasks done
Labels
C-bug Category: Updating dependencies

Comments

@cyqsimon
Copy link

cyqsimon commented Jan 2, 2023

Please complete the following tasks

Rust Version

1.66.0

Clap Version

4.0.32

Minimal reproducible code

use clap::Parser;

#[derive(Clone, Debug, Parser)]
struct CliArgs {
    #[arg(long = "arg", default_value_t)]
    arg: Foo,
}

#[derive(Clone, Debug, Default)]
enum Foo {
    #[default]
    A,
    B,
    Custom(String),
}
impl std::fmt::Display for Foo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Foo::A => write!(f, "Variant A"),
            Foo::B => write!(f, "Variant B"),
            Foo::Custom(s) => write!(f, "Custom: {s}"),
        }
    }
}
impl From<&str> for Foo {
    fn from(value: &str) -> Self {
        match value {
            "A" => Foo::A,
            "B" => Foo::B,
            v => Foo::Custom(v.into()),
        }
    }
}

fn main() {
    let CliArgs { arg } = CliArgs::parse();
    dbg!(arg);
}

Steps to reproduce the bug with the above code

cargo run

Actual Behaviour

arg is Foo::Custom("Variant A").

Expected Behaviour

arg is Foo::A.


I think the main issue here is a gap between user expectation and actual implementation. What I expected (and I think what most users would expect) is that when I use default_value_t, Foo::default is directly called to fetch the default value.

However in reality, it seems like clap is doing a "roundtrip" to string and back when default_value_t is used. The default argument goes from Foo to String via Display, then back to Foo via From<&str>.

The problem with this, is that it assumes Foo::from::<&str> is the inverse operation of Foo::to_string, when that's not necessarily the case. In which case strange behaviour like this suddenly pop up.

So in my amateur opinion, either:

  • the implementation of default_value_t should be changed to use Foo::default directly (this is the ideal solution, but I don't know whether this will be difficult to implement),
  • or the documentation needs to explicitly clarify this requirement that Foo::from::<&str> should be the inverse operation of Foo::to_string
@cyqsimon cyqsimon added the C-bug Category: Updating dependencies label Jan 2, 2023
@epage epage closed this as completed in a87b559 Jan 3, 2023
@epage
Copy link
Member

epage commented Jan 3, 2023

With the current architecture, tracking things to handle this isn't ideal. I've updated the documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Updating dependencies
Projects
None yet
Development

No branches or pull requests

2 participants