Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use #[derive] on a type containing an array where the length is specified by a macro?

Tags:

I have this code:

macro_rules! count {
    () => { 1 };
}

#[derive(Debug)]
struct MyStruct<T> {
    field_list: [T; count!()],
}

The compiler gives this error:

error: `derive` cannot be used on items with type macros
 --> src/main.rs:7:21
  |
7 |     field_list: [T; count!()],
  |                     ^^^^^^^^

Is there any way to use #[derive] on a type containing an array where the length is specified by a macro?

like image 307
Zz Tux Avatar asked May 11 '18 16:05

Zz Tux


1 Answers

Quoting my answer from the Github issue:

It is intentional (here is the historical record), but there is a possibility the situation could be improved in the future, and at least the error message should be rewritten to explain why it refuses to compile.

The underlying issue is that #[derive] macros need to "forward" their trait requirements to all the fields of the struct. For MyStruct to be Debug, the type of field must also be Debug. Consider this one:

#[derive(Debug)] struct MyStruct<T: FromStr> {
    field: T
}

We need to generate impl<T: FromStr> Debug for MyStruct<T> where T: Debug { ... } (you'll see why I picked FromStr in a second). However in this case:

#[derive(Debug)] struct MyStruct<T> {
    field: T::Err
}

Here the field is an associated type, so the generated code actually needs to be impl<T: FromStr> Debug for MyStruct<T> where T::Err: Debug { ... }.

The derive macros actually scan the field types to see whether they need to bound T or an associated type. But if you use a type macro, this breaks. The code generation can't see through the macro, so it doesn't know what bounds to generate.

When this was discovered we couldn't decide whether to let the type macro be expanded eagerly (seems like you could get into a loop or ordering issues), just copy the macro into the where clause (derives normally don't do this because it could expand to a private type, causing type errors in generated code), or something else, so we punted and made it an error.

The problem can't really be fixed while obeying the "policies" of deriving: (1) it generates the bounds for you, and (2) it only generates code that compiles. But since custom derive is stable, there are crates you can use, like derivative, that sidestep the problem by letting you rewrite the bound:

#[derive(Derivative)]
#[derivative(Debug)]
struct MyStruct<T> {
    #[derivative(Debug(bound="T: ::std::fmt::Debug"))]
    field_list: [T; count!()],
}
like image 170
durka42 Avatar answered Oct 11 '22 17:10

durka42