A copypastable guide to implementing simple derive macros in Rust.

Overview

#[derive(MyTrait)]

A copypastable guide to implementing simple derive macros in Rust.

The goal

Let's say we have a trait with a getter

trait MyTrait {
    fn answer() -> i32 {
        42
    }
}

And we want to be able to derive it and initialize the getter

#[derive(MyTrait)]
struct Foo;

#[derive(MyTrait)]
#[my_trait(answer = 0)]
struct Bar;

#[test]
fn default() {
    assert_eq!(Foo::answer(), 42);
}

#[test]
fn getter() {
    assert_eq!(Bar::answer(), 0);
}

So these derives would expand into

impl MyTrait for Foo {}

impl MyTrait for Bar {
    fn answer() -> i32 {
        0
    }
}

Step 0: prerequisites

Install Cargo extended tools

cargo install cargo-edit
cargo install cargo-expand

Step 1: a separate crate for the macro

Proc macros should live in a separate crate. Let's create one in a sub-folder and make it a dependency for our root crate

cargo new --lib mytrait-derive
cargo add mytrait-derive --path mytrait-derive

We should also tell Cargo that mytrait-derive is a proc-macro crate:

cat >> mytrait-derive/Cargo.toml << EOF
[lib]
proc-macro = true
EOF

Step 2: default trait implementation

Now let's make #[derive(MyTrait)] work. We'll need to add a few dependencies to our macro crate

cd mytrait-derive
cargo add [email protected] [email protected]
cargo add [email protected] --features full

And here's our default trait implementation (mytrait-derive/src/lib.rs):

use proc_macro::{self, TokenStream};
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyTrait)]
pub fn derive(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, .. } = parse_macro_input!(input);
    let output = quote! {
        impl MyTrait for #ident {}
    };
    output.into()
}

You can think of ident as a name of a struct or enum we're deriving the implementation for. We're getting it from the parse_macro_input! and then we use it in the quote!, which is like a template engine for Rust code generation.

Now this test (src/lib.rs) should pass:

use mytrait_derive::MyTrait;

trait MyTrait {
    fn answer() -> i32 {
        42
    }
}

#[derive(MyTrait)]
struct Foo;

#[test]
fn default() {
    assert_eq!(Foo::answer(), 42);
}

Also you should be able to find the implementation in the output of cargo-expand

cargo expand | grep 'impl MyTrait'
impl MyTrait for Foo {}

Step 3: the getter initialization

Now it's time to make our getter initializable by #[my_trait(answer = ...)] attribute. We'll need one more crate for convenient parsing of the initialization value

cd mytrait-derive
cargo add [email protected]

Here's the final version of our macro (mytrait-derive/src/lib.rs):

quote! { fn answer() -> i32 { #x } }, None => quote! {}, }; let output = quote! { impl MyTrait for #ident { #answer } }; output.into() } ">
use darling::FromDeriveInput;
use proc_macro::{self, TokenStream};
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[derive(FromDeriveInput, Default)]
#[darling(default, attributes(my_trait))]
struct Opts {
    answer: Option<i32>,
}

#[proc_macro_derive(MyTrait, attributes(my_trait))]
pub fn derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input);
    let opts = Opts::from_derive_input(&input).expect("Wrong options");
    let DeriveInput { ident, .. } = input;

    let answer = match opts.answer {
        Some(x) => quote! {
            fn answer() -> i32 {
                #x
            }
        },
        None => quote! {},
    };

    let output = quote! {
        impl MyTrait for #ident {
            #answer
        }
    };
    output.into()
}

Struct Opts describes parameters of the #[my_trait(...)] attribute. Here we have only one of them - answer. Notice that it's optional, because we don't want to overwrite the default fn answer() implementation if the attribute wasn't used.

The quote! macro is composable - we can use output of one of them in another. So in the match we check if the initializer is passed and create the method implementation or just nothing. And finally we use the result in the outer quote! template.

That's all, clone this repo to play with the code.

You might also like...
Provide expansion of proc-macros, in a way that rustc directs you directly to the issues at hand

expander Expands a proc-macro into a file, and uses a include! directive in place. Advantages Only expands a particular proc-macro, not all of them. I

Macros to make writing proc-macro crates easy

proc-easy Macros to make writing proc-macro crates easy. This crate provides mainly macros and supporting types and traits to reduce amount of boilerp

Macros for candle-lora.

candle-lora-macro This library makes using candle-lora as simple as adding 2 macros to your model structs and calling a method! It is inspired by the

This is a simple Telegram bot with interface to Firefly III to process and store simple transactions.
This is a simple Telegram bot with interface to Firefly III to process and store simple transactions.

Firefly Telegram Bot Fireflies are free, so beautiful. (Les lucioles sont libres, donc belles.) ― Charles de Leusse, Les Contes de la nuit This is a s

A Simple, But amazing telegram bot, Made using the Rust language!

Telegram bot in Rust A fun Telegram bot made using Rust language.

a simple compiled language i made in rust. it uses intermediate representation (IR) instead of an abstract syntax tree (AST).

a simple compiled language i made in rust. it uses intermediate representation (IR) instead of an abstract syntax tree (AST).

A simple entity-component-system crate for rust with serialization support

Gallium A simple entity-component-system crate for rust with serialization support Usage You can include the library using carge: [dependencies] galli

Modrinth API is a simple library for using, you guessed it, the Modrinth API in Rust projects

Modrinth API is a simple library for using, you guessed it, the Modrinth API in Rust projects. It uses reqwest as its HTTP(S) client and deserialises responses to typed structs using serde.

kindly is a simple Rust implementation of a set-user-ID-root program, similar to sudo but in a much reduced way.

kindly is a simple Rust implementation of a set-user-ID-root program, similar to sudo but in a much reduced way.

Owner
Imbolc
Imbolc
Might have a go at implementing Pulumi for Rust 🤷‍♂️

Might have a go at implementing Pulumi for Rust ??‍♂️

Nick 11 Nov 1, 2022
The most primitive and the fastest implementation of a fixed-size last-in-first-out stack on stack in Rust, for Copy-implementing types

This is the simplest and the fastest (faster than Vec!) implementation of a last-in-first-out stack data structure, on stack, when stack elements are

Yegor Bugayenko 10 Jun 18, 2023
Inverse kinematics plugin for Bevy implementing the FABRIK algorithm.

Inverse kinematics plugin for Bevy implementing the FABRIK algorithm.

Georg Friedrich Schuppe 11 Dec 31, 2022
Attribute macro for implementing methods on both Foo and ArchivedFoo.

rkyv_impl Implement methods for Foo and ArchivedFoo in a single impl block. use rkyv::Archive; use rkyv_impl::*; use std::iter::Sum; #[derive(Archive

Duncan 5 Aug 6, 2023
Jonathan Kelley 33 Dec 6, 2022
todo-or-die provides procedural macros that act as checked reminders.

todo-or-die provides procedural macros that act as checked reminders.

David Pedersen 552 Dec 24, 2022
twilight-interactions is a set of macros and utilities to work with Discord Interactions using twilight.

Twilight interactions twilight-interactions is a set of macros and utilities to work with Discord Interactions using twilight. Note: This crate is not

null 24 Dec 26, 2022
Procedural macros for Floccus

floccus-proc Procedural macros for floccus This crate contains procedural attribute macros (currently only one) used by the floccus. But potentially c

ScaleWeather 1 Nov 4, 2021
A metamacro toolkit for writing complex macros.

Big Mac This crate contains the branching_parser! metamacro, which can be used to create complex macros with few lines of code. To use the macro, call

null 1 Nov 14, 2021
proc macros for generating mut and non-mut methods without duplicating code

mwt Hey! You! Read this before using! mwt was thrown together pretty quickly for personal use, because I couldn't find an existing crate that does thi

null 1 Dec 24, 2021