0

How can I expose a struct generated from the quote macro in my derive macro without having to introduce a struct name out of the blue in my usage file (due to macro expansion)?

To illustrate the point, currently, my code looks something like this:

// "/my_derive/lib.rs"
// inside a derive macro function
let tokens = quote! {
  struct MyDeriveMacroInternalStruct {
    variant: #ident_name,
    // other stuff ...
  }
  impl #ident_name {
    pub fn something() -> Vec<MyDeriveMacroInternalStruct> {
      vec![MyDeriveMacroInternalStruct { variant: #ident_name::#variant_name, /*...*/ }, /*...*/]
    }
  }
};

tokens.into()

The usage of my code would look something like this:

use my_derive::MyDerive;

#[derive(MyDerive)]
enum Something {
  A,
  B,
  C,
}

fn process_data() -> Vec<MyDeriveMacroInternalStruct> { // having to write that struct name that came out of nowhere bothers me
  Something::something()
}

fn main() {
  let result = process_data();
  // do stuff...
}

This is a condensed version of my actual code (process_data is in another file). To reiterate my question in light of the example, how can I access the struct without having it randomly appear out of nowhere (due to macro expansion)? To me the code unchanged is hard to understand, read, and change.

I would like to be able to do something like this:

use my_derive::{MyDerive, MyDeriveStruct};

#[derive(MyDerive)]
enum Something {
  A,
  B,
  C,
}

fn process_data() -> Vec<MyDeriveStruct> { // importing the struct instead of magically appearing
  Something::something()
}

fn main() {
  let result = process_data();
  // do stuff...
}

Obviously the idea seems quite stupid, but there has to be a way around it (an arbitrary struct definition). If what I imagined isn't possible, is there some way to be more clear about where the random struct came from?

code
  • 5,690
  • 4
  • 17
  • 39

1 Answers1

2

Actually I thought of something better. Your derive should probably be associated with a trait of the same name.

Add an associated type to your trait:

trait MyDerive {
    type Output;
    ...
}

Then set the associated type when you impl the trait:

  struct MyDeriveMacroInternalStruct {
    variant: #ident_name,
    // other stuff ...
  }

  impl MyDerive for #ident_name {
    type Output = MyDeriveMacroInternalStruct;

    pub fn something() -> Vec<MyDeriveMacroInternalStruct> {
      vec![MyDeriveMacroInternalStruct { variant: #ident_name::#variant_name, /*...*/ }, /*...*/]
    }
  }

Then you can refer to that associated type in return position or wherever:

use my_derive::MyDerive;

#[derive(MyDerive)]
enum Something {
  A,
  B,
  C,
}

fn process_data() -> Vec<<Something as MyDerive>::Output> {
  Something::something()
}

fn main() {
  let result = process_data();
  // do stuff...
}

Note: the convention is for #[derive(Trait)] to correspond to an impl for the given Trait, but your proc macro crate can't export a trait directly for importing in your library code.

So generally the solution is to have two crates:

  • my-trait is the "library" crate which contains the MyTrait trait definition
    • my-trait-derive is the proc-macro crate which contains the derive macro code

my-trait has my-trait-derive as a direct dependency, and re-exports the proc macro from it:

// my-trait lib.rs

pub use my_trait_derive::MyTrait;

// macro and trait names can overlap as they're 
// treated as different item kinds
pub trait MyTrait {
    type Output;
    
    fn something();
}

see how clap does it here (they also re-export the whole clap_derive)

Then a user can use your proc macro + trait like this:

use my_trait::MyTrait;

#[derive(MyTrait)]
enum Something {}

fn process_data() -> Vec<<Something as MyTrait>::Output> {
  Something::something()
}

Older Answer

What I would do is create a trait MyDeriveOutput or something with whatever stuff you want exposed from MyDeriveMacroInternalStruct:

trait MyDeriveOutput {
    fn variant() ...
}

And then generate an impl for each internal struct you create:

struct MyDeriveMacroInternalStruct {
    variant: #ident_name,
    // other stuff ...
}
impl MyDeriveOutput for MyDeriveMacroInternalStruct {
    // whatever
}

Then you can expose the trait and require it to be imported and used with impl Trait in return position:

use my_derive::{MyDerive, MyDeriveOutput};

#[derive(MyDerive)]
enum Something {
  A,
  B,
  C,
}

fn process_data() -> Vec<impl MyDeriveOutput> {
  Something::something()
}

fn main() {
  let result = process_data();
  // do stuff...
}
PitaJ
  • 12,969
  • 6
  • 36
  • 55
  • Sorry, actually I'm not sure how practical your answer is: see, proc macro crates only allow you to export functions with the `proc_macro` or `proc_macro_derive` attributes (you can only export macro expanders). Any solution for that? – code Sep 12 '22 at 17:48
  • You'll have to put the trait in a separate "library" crate. I think generally the "library" crate is used as the main entry point, and the proc macro is re-exported from it. – PitaJ Sep 12 '22 at 18:15
  • Okay, so technically only the original answer works? I'm slightly confused now... – code Sep 12 '22 at 18:20
  • Well both answers require a trait from somewhere, so they don't differ in that factor. It really depends on whether you want your derive to follow the convention of `#[derive(Trait)] == impl Trait for` – PitaJ Sep 12 '22 at 19:47
  • I'm sorry, I've started Rust recently and am not familiar with all the concepts. Would you mind explaining? – code Sep 12 '22 at 20:34
  • Generally, the argument to `#[derive()]` is a trait, and the result of executing that proc macro is implementing the given trait for that type. `#[derive(Debug)] struct Thing` results in `impl Debug for Thing` being generated. This is the `#[derive(Trait)] == impl Trait` convention. So I'd recommend using the pattern in the first answer, and putting `fn something()` on the `MyDerive` trait, rather than implementing it directly on the target type as you are currently. – PitaJ Sep 12 '22 at 20:52
  • 1
    Oh my goodness, it worked perfectly. Thank you so much!! – code Sep 12 '22 at 22:42