Open epage opened 2 years ago
Comment by kbknapp Sunday Nov 12, 2017 at 20:13 GMT
Even without a derivable component we'll need to iron out the constraints that are part of clap which ArgMatches::value_of
accepts instead of &str
Comment by Coder-256 Thursday May 30, 2019 at 06:04 GMT
Is this still being considered? I really like this approach for a number of reasons. I saw the recent blog post didn't mention it which is why I am asking.
I'm not sure if this is possible, and I think it this would rely on rust-lang/rfcs#2593. Anyway I imagine the following scenario. The user defines an enum. Each variant represents an argument. You call ArgMatches::get_value<Variant: Args>() -> &str
where Variant
is the variant type itself (see the linked RFC). That function gives you an instance of T, and you can get information from the associated value.
For example (pseudocode):
// in Clap:
struct Arg<Variant> {
// everything here remains the same
}
// here `Args` is the enum itself
struct App<Args> {
...
fn arg<T: Into<Arg<Self::Args>>>(self, a: T) -> Self
fn get_matches(self) -> ArgMatches<Self::Args>
}
// here `Args` is the enum itself
impl ArgMatches<Args> {
...
fn get_value<Variant: Self::Args> -> &str {
...
}
fn get_values ...
}
// user code (no argument name strings!):
enum MyArgs {
Username,
}
let matches = App<MyArgs>::new("foo")
.arg(
Arg<MyArgs::Username>::new().short("u")
)
.get_matches();
let username = matches.get_value::<MyArgs::Username>();
Alternatively, if the Rust team ends up deciding to allow you to restrict enum variants more (not currently planned AFAIK):
// in Clap:
// here `Args` refers to a variant of an enum
struct Arg<Variant: Args> {
fn new_single() { ... } // Only allowed if the variant has an associated string
fn new_multiple() { ... } // Only allowed if the variant has an associated iterator
fn new_boolean() { ... } // Only allowed if the variant has an associated boolean
}
// here `Args` is the enum itself
impl ArgMatches<Args> {
...
fn get_value_automatic<Variant: Self::Args> -> Variant {
...
}
}
// user code (no argument name strings!):
enum MyArgs {
Usernames(IntoIterator<String>),
}
let matches = App<MyArgs>::new("foo")
.arg(
Arg<MyArgs::Usernames>::new_multiple().short("u")
)
.get_matches();
let MyArgs::Usernames(usernames) = matches.get_value();
This way, there is a single function get_value
that returns the appropriate return type for any variant you give it. That means for example you can't pass a multi-argument to ArgMatches::value_of
like you can today.
Anyway this is all still up in the air anyway since it seems like the Rust team hasn't settled on exactly what features they plan to add to enums. What do you think?
Comment by epage Monday Jul 19, 2021 at 18:56 GMT
I tried to read up on past effort for enums but the blog post was removed as part of the new site and for some reason the wayback machine doesn't have that one article
@kbknapp happen to have this hanging around somewhere?
Comment by kbknapp Monday Jul 19, 2021 at 19:09 GMT
I believe I do. I'm on mobile at the moment and will look it up shortly once I get to a computer.
Comment by kbknapp Monday Jul 19, 2021 at 21:07 GMT
I found it.
Comment by epage Tuesday Jul 20, 2021 at 14:01 GMT
@pksunkara I propose this issue and #1663 be deferred to the clap4 milestone.
Comment by epage Tuesday Jul 20, 2021 at 15:14 GMT
With that out of the way, some quick thoughts:
Clap has several nearly distinct APIs
In considering our care abouts, we need to consider each API and what the expected role of it is.
For example, if the expectation is someone will probably use the derive, unless they want to get rid of the overhead of proc macros, then derives for custom keys (#1663) seems contradictory.
Similarly, the derive API prevents the problems with stringly typed APIs. On a related note, I'm unsure if we can generate the needed enums for the derive API to use anything but strings. We can't see through to other enums and structs to generate a global list. We'd need to support a composable enum and then somehow allow all of the typing for App
with subcommands to magically work. This would also bloat the binary because we'd have multiple copies of App and ArgMatches being compiled.
As for performance, while keys use less memory, &'static str
are fast to compare by equality. I suspect Rust is short-circuiting with a pointer check. We just can't use it because sometimes people use generated strings.
We might be able to resolve the generics problem via type aliases (e.g. pub type App = BaseApp<&'static str>
).
One problem with enums is that we default value_name
to be the name
. We can't do this with enums unless we require a Display
or as_str()
on it.
Regarding the hash work in the above blog post, one concern brought up at the time was collisions. I can't find the discussions to see how that was resolved.
I propose we rename App
et all and make them generic. We then provide type aliases that set the generic parameter to &'static str
. This will require those who need allocations to go through more hoops, though they could choose to use kstring::KString
(#1041).
See playground for example code
Benefits
&'static str
for avoiding allocations and for fast lookups)&'static str
vs hash vs enum, etc) and choose what works bestDownsides
Comment by epage Tuesday Jul 20, 2021 at 17:22 GMT
Just wanted to call out https://github.com/clap-rs/clap/issues/604 is the issue for "error on typo"
Issue by kbknapp Sunday Nov 12, 2017 at 20:11 GMT Originally opened as https://github.com/clap-rs/clap/issues/1104
I.e. remove the "stringly typed" nature of clap
How this is done internally is up for debate and the reason for this issue. See the related topic #1041
Basically we want to Hash whatever key is given and store that instead of a
&str
/String
/Cow<_>
.