6

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?

Zz Tux
  • 600
  • 1
  • 5
  • 18
  • Possible duplicate of [Is there a way to count with macros?](https://stackoverflow.com/questions/33751796/is-there-a-way-to-count-with-macros) – Stargateur May 11 '18 at 16:12
  • @Stargateur No, these problems are totally different. – Zz Tux May 11 '18 at 16:23
  • I do not think this is a duplicate of "Is there a way to count with macros?" The code has already solved how to count (for some form of counting), but the problem occurs after that. – Shepmaster May 11 '18 at 16:24
  • What is also strange is that the same code works if the generic type is replaced with a concrete one, such as `i32`. – Shepmaster May 11 '18 at 16:24
  • @ZzTux well, your very first question was "How to specify array size by macro in Rust?", but I let the link it's maybe just related. – Stargateur May 11 '18 at 16:32
  • @Shepmaster [It works on non-generic type as you said.](https://play.rust-lang.org/?gist=49f0c890c0fc8998f2985174990db9ee&version=stable&mode=debug) Maybe this is a compiler bug? Should I open an issue on Github? – Zz Tux May 11 '18 at 16:36
  • 1
    @Shepmaster It seems to fail when the struct is generic, even if the array element type is concrete. [You don't even need arrays.](https://play.rust-lang.org/?gist=b058f3b36f5a8b070857b873c0840543&version=stable&mode=debug) – interjay May 11 '18 at 17:18
  • I opened an issue on Github: https://github.com/rust-lang/rust/issues/50676 – Zz Tux May 12 '18 at 02:47

2 Answers2

5

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!()],
}
durka42
  • 1,502
  • 10
  • 17
1

A solution could be to use two macros, one macro that defines the struct and another macro that defines the size.

macro_rules! foo {
    ($x:expr) => {
        #[derive(Debug)]
        struct MyStruct<T> {
            field_list: [T; $x],
        }
    };
}

macro_rules! bar {
    () => {
        foo!(1);
    };
}

bar!();

It works on non-generic type as you said. Maybe this is a compiler bug? Should I open an issue on Github?

This is probably not a bug, but certainly a limitation of macros. You could however open an issue to ask to improve it.

trent
  • 25,033
  • 7
  • 51
  • 90
Stargateur
  • 24,473
  • 8
  • 65
  • 91