code
code

Reputation: 6349

Expose struct generated from quote macro without appearing out of nowhere

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?

Upvotes: 0

Views: 713

Answers (1)

PitaJ
PitaJ

Reputation: 15074

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...
}

Upvotes: 2

Related Questions