Building a derivable trait with multiple associated functions

I'm trying to build an ORM such that one will only need to derive a trait (IntoModel) to convert a struct to a workable SQL table.

The derived trait will allow performing operations similar to this

use xorm::IntoModel;
use xorm_macro::IntoModel;

#[derive(IntoModel)]
struct UserInformation {
    name: String,
    age: u64,
}



fn main() {
    // create a new user
    UserInformation::create();
    //find or create a new user
    UserInformation::find_or_create();
    // find user by primary key
    UserInformation::find_by_pk();
    // delete a model record
    UserInformation::destroy();
}

So far, I've been able to achieve this using default implementations thus:

pub trait IntoModel {
    /// create a new model
    fn create() {
        println!("create a new record")
    }

    /// find all record
    fn find(condition: String) {
        println!("the find model where {}", condition)
    }

    /// find record, if not fount create
    fn find_or_create() {
        println!("the find or create associated function")
    }
    
    /// find record by primary key
    // TODO: find a way to deduce the primary key
    fn find_by_pk() {
        println!("the find by pk associated function")
    }

    /// delete a model record
    fn destroy() {
        println!("Delete a model record")
    }
}

However, I'm stuck implementing each of the associated functions,
I understand from an experiment that I can't have something such as this, where I call on #[proc_macro_derive(IntoModel)] multiple times to achieve the implementation

use proc_macro::TokenStream;
use syn;

mod destroy;
mod find;
mod find_by_pk;

#[proc_macro_derive(IntoModel)]
pub fn find_by_pk_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    find_by_pk::impl_macro(&ast)
}


#[proc_macro_derive(IntoModel)]
pub fn find_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    find::impl_macro(&ast)
}

So, I'm thinking if it is possible to use an enum in place of <implementation>::impl_macro(&ast) and then match the enums variant with the corresponding functions that will implement the associated function. Or if there is another way to achieve the desired effect

I'm not entirely clear on what the problem you're running into is. You can just implement all of the associated functions in a single derive macro. Is there a problem you're hitting if you do that?

1 Like

the problem is linking the associated function to the derivable trait. (I'm not sure I put the terms right). So far, I have:

use proc_macro::TokenStream;
use syn;

mod destroy;
mod find;
mod find_by_pk;

#[proc_macro_derive(IntoModel)]
pub fn find_by_pk_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    find_by_pk::impl_macro(&ast)
}

But when I try linking the actual implementation imported from the destroy, find and find_by_pk, modules thus

use proc_macro::TokenStream;
use syn;

mod destroy;
mod find;
mod find_by_pk;

#[proc_macro_derive(IntoModel)]
pub fn find_by_pk_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    find_by_pk::impl_macro(&ast)
}

#[proc_macro_derive(IntoModel)]
pub fn find_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    find::impl_macro(&ast)
}

#[proc_macro_derive(IntoModel)]
pub fn destroy_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    destroy::impl_macro(&ast)
}

Note that, each of the modules has a function impl_macro that holds the desired implementation. However, doing this gives this error

 Compiling xorm_macro v0.1.0 (/home/drizzle/devs/xorm/xorm_macro)
error[E0428]: the name `IntoModel` is defined multiple times
  --> src/lib.rs:28:21
   |
22 | #[proc_macro_derive(IntoModel)]
   |                     --------- previous definition of the macro `IntoModel` here
...
28 | #[proc_macro_derive(IntoModel)]
   |                     ^^^^^^^^^ `IntoModel` redefined here
   |
   = note: `IntoModel` must be defined only once in the macro namespace of this module

error[E0428]: the name `IntoModel` is defined multiple times
  --> src/lib.rs:34:21
   |
22 | #[proc_macro_derive(IntoModel)]
   |                     --------- previous definition of the macro `IntoModel` here
...
34 | #[proc_macro_derive(IntoModel)]
   |                     ^^^^^^^^^ `IntoModel` redefined here
   |
   = note: `IntoModel` must be defined only once in the macro namespace of this module

For more information about this error, try `rustc --explain E0428`.
error: could not compile `xorm_macro` due to 2 previous errors

You only need to parse the AST once and emit a single impl block with all of the associated functions in it.

3 Likes

Yes, I figured that I need to parse the AST once, But I can't seem to figure out how best to achieve this. One thing that came to my mind was using an enum, then matching the variants of the enum with the associated function.

You don't need any of that. You write a single derive macro in a single function, and then literally emit all of the trait impl all at once. For example:

quote! {
    impl Foo for Bar {
        fn func_one() {
        }

        fn func_two() {
        }
    }
}

I don't know why you think that you need more than one macro or more than one invocation; you can emit whatever code you want from a macro, including an impl block with several associated functions.

1 Like

that works! Thanks

thank you so much. It's fixed

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.