# I wrote my first custom derive macro

**URL:** <https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887>\
**Category:** code review\
**Created:** [April 4, 2021, 4:40pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887 "2021-04-04T16:40:48Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [April 4, 2021, 4:40pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/1 "2021-04-04T16:40:48Z")

</div>

For a solution to Exercism's [Space Age](https://exercism.io/tracks/rust/exercises/space-age) exercise.

I stayed well clear of writing custom macros for a long time. They seemed awesome, but they must be really complicated, I thought. After all, they're more powerful than macro\_rules! macros and those look like confusing.

Well, I finally tried it and it wasn't nearly as bad as I thought it would be. Several times I crossed my finger and ran `cargo build` to find... no errors, it just worked? So I'm very chuffed and thought I'd share the news.

The [space-age-derive](https://github.com/drmason13/space-age-derive/blob/master/src/lib.rs) crate is on github.

I managed to run into and then avoid one pitfall. I needed an attribute to implement this trait, to fill in an associated constant. So I wanted to do basically

```rust
#[my_constant = 12]
struct Foo

```

and have that impl the trait for me. I did this using a bare attribute macro (which you can see in the history on github if you're curious). But to get it to work it needed a couple of janky things. One, to use `crate::MyTrait` to reference the trait I was implementing. So it only worked since the trait was defined in the crate consuming my macro. Otherwise it would complain about missing imports. The other thing was it returned both the struct _and_ the trait impl which seemed wrong.

Then I realised/remembered that [derive macro helper attributes](https://doc.rust-lang.org/reference/procedural-macros.html#derive-macro-helper-attributes) were a thing i.e. `#[proc_macro_derive(MyTrait, attributes(my_helper_attr))]` and now it's a proper derive macro, much cleaner!

Also, the Syn and Quote crates are fantastic! They can be intimidating at first, but it's worth diving in, referring to **examples** and reading the docs _for what you need to use_ - I don't think I could read through the whole Syn docs, it's a big crate 😛

There's lots about proc macros I don't understand yet, and my derive is _very_ simple, but I did it and feel like I can add custom derives to my toolbox - yay! 😃

---

<div class="post-metadata">

**Author:** ![Kestrer](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/kestrer/32/22688_2.png) [@Kestrer](https://users.rust-lang.org/u/Kestrer)\
**Post date:** [April 4, 2021, 5:25pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/2 "2021-04-04T17:25:45Z")

</div>

First of all, `.filter(...).next()` is better expressed with just `.find(...)`.

Secondly, is this a public macro, or is it just going to be used internally? If it's just internal the following issues don't apply:

- `quote! { impl crate::Planet ` won't work unless the user has a trait `Planet` in their crate root. Getting the name of the containing crate is difficult, but the best solution is to accept a special `#[crate = ::path::to::crate]` helper attribute, and default to `::crate_name` if it's not provided.
- Try to avoid `panic!` for error handling in proc macros; instead, expand to a [`::core::compile_error!`](https://doc.rust-lang.org/stable/core/macro.compile_error.html) invocation. If you use `quote_spanned!` to give the `compile_error!` a specific span you can change where the error message points to in the compiler output.
- The `parse_meta` match can be improved slightly with a custom parser:

```rust
let orbital_period = (|input| {
    input.parse::<Token![=]>()?;
    input.parse::<LitFloat>()
})
.parse2(attr.tokens.clone())
.unwrap();

```

---

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [April 4, 2021, 7:12pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/3 "2021-04-04T19:12:53Z")

</div>

Thank you for the feedback, these are good points.  
I will switch to use find, that was rather lazy of me, the assumption was there is only one attr.

This isn't meant to be seriously used but I'm interested in learning how to make it suitable for public use.

> [@Kestrer](#):
>
> Getting the name of the containing crate is difficult

I'm keen to try a few things out here. I didn't mean to use `crate::Planet`, I was hoping just `space_age::Planet` would work. EDIT: It does not

I'm glad there are better ways to handle errors in proc macros, I want sure what to do since the function doesn't return a result, now I know.

---

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [April 4, 2021, 7:19pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/4 "2021-04-04T19:19:33Z")

</div>

I'm curious how Dtolnay's [heapsize example](https://github.com/dtolnay/syn/blob/master/examples/heapsize/heapsize_derive/src/lib.rs) works then, on the surface it looks like naming the crate should "just work":

```rust
    let expanded = quote! {
        // The generated impl.
        impl #impl_generics heapsize::HeapSize for #name #ty_generics #where_clause {
            fn heap_size_of_children(&self) -> usize {
                #sum
            }
        }
    };

```

Oh I think I get the difference. heapsize is _always_ in scope when you use `[#derive(HeapSize)]` because the trait you're implementing is part of the public API. Whereas I have made a derive crate for internal use and it seems I can't refer to my own crate's Trait in the same way. Maybe the heapsize example being a workspace also helps in some way.

Update...  
I've juggled stuff around and now have a [workspace](https://github.com/drmason13/space-age) with 3 crates:

- 1 to hold the types, like the `Planet` trait I want to derive
- 1 for the derive proc macro
- 1 "consumer" that imports the derive macro and uses it to solve the exercise

This meant I could hardcode the crate name in the derive macro just like the heapsize example. I think this is probably a pretty safe bet if you want other people to be able to use _your_ trait and also derive it. It seems less common that you'd write a derive macro for someone else's trait so I'm happy with the approach.

---

<div class="post-metadata">

**Author:** ![Kestrer](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/kestrer/32/22688_2.png) [@Kestrer](https://users.rust-lang.org/u/Kestrer)\
**Post date:** [April 4, 2021, 9:09pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/5 "2021-04-04T21:09:17Z")

</div>

You should be able to use `crate::Planet` in the expansion of the derive macro if you only use the derive macro within the crate that has `Planet` defined in its crate root.

---

<div class="post-metadata">

**Author:** ![Yandros](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/yandros/32/8136_2.png) [@Yandros](https://users.rust-lang.org/u/Yandros)\
**Post date:** [April 4, 2021, 9:12pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/6 "2021-04-04T21:12:03Z")

</div>

See:

> [@Proc macros - using third party crate](https://users.rust-lang.org/t/proc-macros-using-third-party-crate/42465/4):
>
> The Rust solution to this, more general than just to procedural macros, are public dependencies through public reexports: //! Your `lib.rs` // 2018 edition pub use ::itertools; // old style pub extern crate itertools; and then within a macro\_rules! macro, for instance, you expand to a path referring to your crate first, which must contain the itertools crate as the second element of the path / as if it were a top-level module: macro\_rules! my\_izip {( $($input:tt)\* ) =\> ( $crate // …

for a description of the frontend (with traits, re-exporting proc-macro, …) + backend (proc-macro only) crates pattern.

If the frontend crate (say, `heapsize`, or `space_age`) happens to call the proc-macro itself, you can put `extern crate self as space_age;` at the root of its `lib.rs` to allow referring to your own crate as if it were an external one, making the proc-macro thus usable internally 🙂

---

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [April 4, 2021, 9:12pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/7 "2021-04-04T21:12:18Z")

</div>

I'm struggling to use `compile_error!`. My first attempt ended up raising compiler erros (perhaps unsurprisngly!) but then I found out it's a [bodge](https://stackoverflow.com/questions/54392702/how-to-report-errors-in-a-procedural-macro-using-the-quote-macro) until the Diagnostics API is stable.

The awkward thing is arranging it so that the `compile_error!` macro is only called inside `quote_spanned!`, whilst also having the span I want to use. The problem for me is I error before I get the LitFloat I want the span of... argh 😃

---

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [April 4, 2021, 9:14pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/8 "2021-04-04T21:14:05Z")

</div>

> [@Yandros](#):
>
> you can put `extern crate self as space_age;` at the root of its `lib.rs` to allow referring to your own crate as if it were an external one, making the proc-macro thus usable internally 🙂

ah just what I needed, that is a handy trick. I'm not surprised rust/cargo had a way to do this. Not something I've come across needing before 🙂

---

<div class="post-metadata">

**Author:** ![Yandros](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/yandros/32/8136_2.png) [@Yandros](https://users.rust-lang.org/u/Yandros)\
**Post date:** [April 4, 2021, 9:16pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/9 "2021-04-04T21:16:36Z")

</div>

To report errors, something along the following lines ought to work:

```diff
    // Parse the argument to the attribute as a float
    // let orbital_period: LitFloat = attr.parse_args().unwrap();
    let orbital_period: LitFloat = match attr.parse_meta().unwrap() {
        syn::Meta::NameValue(name_value) => {
            if let syn::Lit::Float(x) = name_value.lit {
                x
            } else {
- panic!("expected a float value, e.g. #[orbital_period = 1.0]")
+ return syn::Error::new_spanned(
+ &name_value.lit,
+ "expected a float value, e.g. #[orbital_period = 1.0]",
+ )).to_compile_error().into()
            }
        },
- _ => panic!("expected a name = value style syntax, e.g. #[orbital_period = 1.0]")
+ extraneous => return Error::new_spanned(
+ &extraneous,
+ "expected a name = value style syntax, e.g. #[orbital_period = 1.0]",
+ ).to_compile_error().into(),
    };

```

If you want a `panic!`-like interface, when using it inside a function returning a `TokenStream`, you can define:

```rust
macro_rules! bail {( $err_msg:expr $(=> $span:expr)? $(,)? ) => (
    {
        let mut _span = ::proc_macro2::Span::call_site();
        $( _span = $span; )?
        return ::syn::Error::new(_span, $err_msg.as_str())
                   .to_compile_error()
                   .into()
        ;
    }
)}

```

so as to write:

```rust
=> bail!("expected a (literal) float value, …" => name_value.lit.span())
…
=> bail!("expected a …, etc." => extraneous.span())

```

* * *

> [@drmason13](#):
>
> Not something I've come across needing before 🙂

That feature was added precisely with (derive) proc-macros in mind 😉

---

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [April 4, 2021, 9:36pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/10 "2021-04-04T21:36:53Z")

</div>

perfect! 🙂

```rust
error: expected a float value, e.g. #[orbital_period = 1.0]
  --> space-age\src\lib.rs:14:20
   |
14 | #[orbital_period = 1]
   | ^

error: expected a name = value style syntax, e.g. #[orbital_period = 1.0]
  --> space-age\src\lib.rs:18:3
   |
18 | #[orbital_period(0.61519726)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^

```

Thank you so much for the help!

---

<div class="post-metadata">

**Author:** ![drmason13](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/drmason13/32/20629_2.png) [@drmason13](https://users.rust-lang.org/u/drmason13)\
**Post date:** [May 5, 2021, 10:04pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/11 "2021-05-05T22:04:21Z")

</div>

I went back over this and tried the "backend - frontend" project structure. It worked well, and I think for libraries that provide traits with their own custom derives it is the best way to go, feels smoother only needing one (public) crate.

I've updated my [space-age repo](https://github.com/drmason13/space-age) with what that looks like. I also used your bail! macro Yandros, feels very ergonomic, and it helped the `proc_macro` and `proc_macro2` thing click for me. Proc macros really do have their own special rules:

> Types from `proc_macro` are entirely specific to procedural macros and cannot ever exist in code outside of a procedural macro. Meanwhile `proc_macro2` types may exist anywhere including non-macro code

from the [proc\_macro2 docs](https://docs.rs/proc-macro2/1.0.26/proc_macro2/index.html)

`proc_macro2` is needed in Yandros' bail! macro because it is in its own separate macro\_rules macro and not part of the procedural macro. It is just a copy of `proc_macro`, just one you're allowed to use outside of procedural macros.

---

<div class="post-metadata">

**Author:** ![system](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/system/32/51372_2.png) [@system](https://users.rust-lang.org/u/system)\
**Post date:** [August 3, 2021, 10:04pm UTC](https://users.rust-lang.org/t/i-wrote-my-first-custom-derive-macro/57887/12 "2021-08-03T22:04:27Z")

</div>

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.
