Introduction: Using derive-deftly for easy derive macros.

derive-deftly is a Rust package that you can use to define your own derive macros without having to write low-level procedural macros.

The syntax is easy to learn, but powerful enough to implement macros of significant complexity.

Here's a simple example, to help you get a feel for the system.

Example: deriving field accessor functions

Suppose you want to add accessor functions for the fields in your struct. You could do it by hand, like this:

#![allow(unused)]
fn main() {
struct MyStruct {
   a: Vec<u8>,
   b: (u32, u32),
   c: Option<u16>,
   d: (u32, u32),
   // (more fields here ...)
}
impl MyStruct {
    fn get_a(&self) -> &Vec<u8> {
        &self.a
    }
    fn get_b(&self) -> &(u32, u32) {
        &self.b
    }
    fn get_c(&self) -> &Option<u16> {
        &self.c
    }
    fn get_d(&self) -> &(u32, u32) {
        &self.b
    }
    // (more accessors here...)
}
}

But this is time consuming, and potentially error-prone. (Did you see the copy-paste error in get_d?) If you had to define a large number of accessors like this, or do it for a bunch of types, you might want an easier way.

(Of course, in this case, you could use an existing crate. But let's suppose that there was no crate meeting your needs, and you had to build your own.)

Here's how you could define and use a derive-deftly template to save some time and risk.

#![allow(unused)]
fn main() {
use derive_deftly::{define_derive_deftly, Deftly};

// Defines the template
define_derive_deftly! {
    Accessors:
    // Here, $ttype will expand to the toplevel type; in this case,
    // "MyStruct".
    impl $ttype {
        // This $( ... ) block will be repeated: in this case, once per field.
        $(
            // Here we define a "get_" function: In this definition,
            // $ftype is the type of the field,
            // $fname is the name of the field,
            // and $<...> denotes token pasting.
            fn $<get_ $fname>(&self) -> &$ftype {
                &self.$fname
            }
        )
    }
}

// Applies the template to your struct
#[derive(Deftly)]
#[derive_deftly(Accessors)]
struct MyStruct {
   a: Vec<u8>,
   b: (u32, u32),
   c: Option<u16>,
   d: (u32, u32),
   // (more fields here...)
}
}

Note 1:

This example is deliberately simplified for readability. As written here, it only works for structs with no generic parameters. Later on, we'll learn how to write templates that work to enums, generic types, and more.

Note 2:

Some of the accessors above aren't the ones you'd write yourself in idiomatic Rust: You'd probably want to return &str instead of &String, and &[u8] instead of &Vec<u8>.

Once again, we'll be able to do this more idiomatically once we're more familiar with derive-deftly.

What, exactly, can derive-deftly do?

The derive-deftly crate helps you do a lot of neat stuff:

  • You can define sophisticated templates that apply, like derive() macros, to structs, enums, and unions.
  • You can apply your templates to your own structs, enums, and unions.
  • You can export your templates for use by other crates.
  • Your templates can define new types, functions, methods, and variables: any kind of item that Rust allows.
  • Within your templates, you can look at nearly everything about the input type: fields, variants, attributes, types, and so on.
  • Your templates can use complex control structure to define configurable behavior or special cases.

Still, there are a number of things that derive-deftly cannot do, or cannot do yet:

  • You can't apply a derive-deftly template to anything besides a struct, enum, or union. For example, you can't apply it to a fn declaration or an impl block.
  • Like a derive macro, a derive-deftly template cannot change the type it's applied to: You can define a new type, but you can't change the definition of the original type.
  • The derive-deftly template language, though powerful, does not attempt to be a general-purpose programming language. There will always be some problems better solved through procedural macros, or through changes to the Rust language.
  • Because of limitations in the Rust macro system, derive-deftly templates have to be applied using the syntax above. (That is, in the example above, you need to say #[derive(Deftly)] and #[derive_deftly(Accessors)]. You can't define a macro that delivers #[derive(Accessors)] directly.

About this book

In the rest of this book, we'll explain how to use derive-deftly in detail. We'll try to explain each of its features, and how to use it to make correct, reliable templates that handle a broad range of situations.

Other resources

There are other resources on derive-deftly:

  • The Reference tries to provide is a terse but complete (and farily rigorous) guide to derive-deftly's behavior and features.
  • The rustdoc describes the exposed members of the derive-deftly crate, along with other useful information.