Structopt: Structopt derive doesn't like type-parametric structs

Created on 26 Jul 2018  Â·  12Comments  Â·  Source: TeXitoi/structopt

Probably better showed with some code:

struct Wrapper<O> {
    #[structopt(flatten)]
    sub: O,
}

I get this error message:

error[E0412]: cannot find type `O` in this scope
  --> src/lib.rs:31:10
   |
31 | #[derive(StructOpt)]
   |          ^^^^^^^^^ did you mean `OW`?

error[E0243]: wrong number of type arguments: expected 1, found 0
  --> src/lib.rs:31:10
   |
31 | #[derive(StructOpt)]
   |          ^^^^^^^^^ expected 1 type argument

Similar thing works with serde, where it automatically places correct type bounds on the generated impl block.

enhancement help wanted

Most helpful comment

@TeXitoi actually, there is a use case where this would be pretty useful.

What if we just copy-paste the bounds?


#[derive(StructOpt)] 
struct Opt<T, U: Read> {}

// expands to 
impl<T, U: Read> StructOpt for Opt<T, U> {
    // ...
}

I understand that it's not enough in some corner cases (like type items, implicit Sized stuff and all) and yet we will have 99% covered, including the case above.

As an alternative, we could mimic serde:

// `T: StructOpt` will be added automatically 
// since there's no `#[structopt(bound(...))]` annotation  
// But no bounds for `U` will be generated since it's `skip`
#[derive(StructOpt)]
struct StructOpt<T, U> {
    arg: T,

    #[structopt(skip)]
    skipped: U
}

// `T: StructOpt` will NOT be added  
// since there's an explicit `#[structopt(bound(...))]` annotation  
#[derive(StructOpt)]
#[structopt(bound("S: FromStr + StructOpt"))]
struct StructOpt<S, T> {
    #[structopt(parse(from_str))]
    arg: S,

    #[structopt(skip)]
    skipped: T,
}

Both alternatives are pretty easy to implement

All 12 comments

StructOpt was not design with generics in mind. That's a complicated thing.

What is your use case?

Yes, I already noticed it's a bit harder than just making sure the impl gets the type bounds, because some things are not part of the trait. But I've found a workaround for my own use case.

I'm building a helper start-me-a-daemon library. Some of the options will be specific for each daemon, while some others will be common for all of them (like logging and option not to go to background). So I want to compose the common ones (defined in the library) with the daemon-specific ones provided by the user of the library as a generic.

For now, I have this (I haven't tried it yet, but I guess it should work).

Yes, but that's a different use case ‒ they want to expose the verbosity flag to the end application. I don't want to give the application the logging configuration etc ‒ I just want to logging to be configured automagically. That means my library needs access to the common configuration (if I went the verbosity way, I'd have to let the user tell me where the logging configuration is) and it would pollute the user's configuration structure.

(Oh, I haven't included the link in the post up there ↑ ‒ this is what I do now: https://github.com/vorner/spirit/blob/master/src/lib.rs#L59)

Great workaround!

Let's get back to the original issue. Do we want to support generic structs and what the pitfalls are?
Or will we just close it as "wontfix"?

wontfix is fine for me.

@TeXitoi actually, there is a use case where this would be pretty useful.

What if we just copy-paste the bounds?


#[derive(StructOpt)] 
struct Opt<T, U: Read> {}

// expands to 
impl<T, U: Read> StructOpt for Opt<T, U> {
    // ...
}

I understand that it's not enough in some corner cases (like type items, implicit Sized stuff and all) and yet we will have 99% covered, including the case above.

As an alternative, we could mimic serde:

// `T: StructOpt` will be added automatically 
// since there's no `#[structopt(bound(...))]` annotation  
// But no bounds for `U` will be generated since it's `skip`
#[derive(StructOpt)]
struct StructOpt<T, U> {
    arg: T,

    #[structopt(skip)]
    skipped: U
}

// `T: StructOpt` will NOT be added  
// since there's an explicit `#[structopt(bound(...))]` annotation  
#[derive(StructOpt)]
#[structopt(bound("S: FromStr + StructOpt"))]
struct StructOpt<S, T> {
    #[structopt(parse(from_str))]
    arg: S,

    #[structopt(skip)]
    skipped: T,
}

Both alternatives are pretty easy to implement

I'm reopening this since it looks like there are people interested in this feature out there (#317).

A PR implementing this serde-like design will be accepted:

// Every generic that occurs in a field annotated with 
// either `#[structopt(flatten)]` or `#[structopt(subcommand)]` 
// will be given `G: StructOpt` bound by default 
// (i.e when no `#[structopt(bound(...))]` is present).
//
// `T: StructOpt` will be added automatically
// since there's no `#[structopt(bound(...))]` annotation and 
// it occurs in `#[structopt(flatten)]`. Same story for `U` (because of `subcommand`).
//
// No `StructOpt` bounds for B should be generated 
// since none of the fields it occurs in
// are marked with `subcommand` nor `flatten`.
//
// Additionally, each field marked with `#[structopt(parse(...))]` 
// (with no explicit function) has to be bounded with one of
//
// * G: std::convert::From<&'_ str> (for `from_str`)
// * G: std::str::FromStr<&'_ str> (for `try_from_str`, including implicit)
// * G: std::convert::From<&'_ OsStr> (for `from_os_str`)
// * G: std::convert::From::from<bool> (for `from_flag`)
//
// No "parse" bounds will be generated for `from_occurences` (subject for future design)
// and `try_from_os_str`, as well as no "parse" bounds will be generated for explicit 
// `#[structopt(parse(type = explicit::function))]`
// 
#[derive(StructOpt)]
struct ImplicitBounds<T, U, B> {
    #[structopt(flatten)]
    arg: T,

    #[structopt(subcommand)]
    subcmd: U,

    #[structopt(skip)]
    skipped: B,

    other: B,

    // implicit `parse(try_from_str)`
    //
    // T: std::str::FromStr<&'_ str>
    arg2: T, 

    // U: std::convert::From<&'_ str>
    #[structopt(parse(from_str))]
    arg3: U,

    // B: std::convert::From<&'_ OsStr>
    #[structopt(parse(from_os_str))]
    arg4: B,

    // A: std::convert::From::from<bool>
    #[structopt(parse(from_flag))]
    arg5: A,
}

// Generated:
impl<T, U, B, A> StructOpt for ImplicitBounds<T, U, B, A> 
where
    T: StructOpt + FromStr<&'_ str>,
    U: StructOpt + From<&'_ str>,
    B: From<&'_ OsStr>,
    A: From::from<bool>
{
    // ...
} 


// User is able to override the automatic bounds via 
// `#[structopt(bound(...))]` attribute
//
#[derive(StructOpt)]
struct ExplicitBounds<S, T, U> {
    #[structopt(bound(S: FromStr + AnotherTrait)), parse(from_str))]
    arg: S,

    #[structopt(flatten)]
    #[structopt(bound(T: StructOpt + AnotherTrait))]
    flatten: T,

    // The bounds will be generated automatically 
    // since there's no `bound` attribute present 
    error: U 
}

// Generated:
impl<S, T, U> StructOpt for ExplicitBounds<S, T, U> 
where
    T: StructOpt + AnotherTrait,
    S: FromStr + AnotherTrait,
    U: FormStr
{
    // ...
}

cc @TeXitoi do you agree with this design?

I don't think bound should be on fields, more on the struct.

Automatic bound seems complicated to anticipate for the user.

It might complexify the code a lot.

I'm afraid it will be a lot of work for little usage.

I'm not totally against this feature, but it must be simple enough to understand, implement, document and maintain.

I must confess, I just stole the design from serde.

I don't think bound should be on fields, more on the struct.

I think that "fields vs struct bound attr" is quite irrelevant here, either will do.

Automatic bound seems complicated to anticipate for the user.

Not really. I tried to design the automatic bounding so it will work in 99% cases, as any good heuristic would. Thus, in 99% cases users shouldn't even be aware of it since everything "just works".

Explicit bound attributes are for cases like Foo<T>, where it's unclear what to do: Foo<T>: Trait vs T: Trait (i.e if the generic is just a part of the type, not the type itself). In such cases, no bounds will be generated anyway ¯\_(ツ)_/¯

I honestly can't see a case where this auto bounding may misfire, can you?

It might complexify the code a lot. I'm afraid it will be a lot of work for little usage.

Not really. In essence, all we need is:

  • Traverse the graph (already implemented)
  • Collect the info (partially implemented)
  • Pass it to the top level (this is where dragons be, but should be fairly easy after the refactoring).
  • Generate and emit the where clause (easy).
Was this page helpful?
0 / 5 - 0 ratings

Related issues

mbrobbel picture mbrobbel  Â·  5Comments

TedDriggs picture TedDriggs  Â·  6Comments

epage picture epage  Â·  7Comments

rask picture rask  Â·  8Comments

coder543 picture coder543  Â·  9Comments