[ docs: crate top-level | overall toc, macros | template etc. reference | guide/tutorial ]

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.

[ docs: crate top-level | overall toc, macros | template etc. reference | guide/tutorial ]

Getting started

In this short chapter, we'll talk about a few things you should know before you start using derive-deftly, and we'll show you a simple "hello world" example.

What you should know before you start

Before you start using derive-deftly, there are a few things t2hat this guide assumes you are already familiar with. Don't feel bad if you don't know this stuff already; just make a note to come back later once you're ready.

  • We assume that you are already familiar with the Rust programming language and the Cargo package management tool. If you're not, maybe try the Rust Book or Rust By Example.

  • We assume that you are comfortable writing, building, compiling, and debugging Rust programs.

  • We assume that you are already familiar with the macro_rules! syntax for defining "macros by example". If you're not, see the relevant sections in the above books.

If these assumptions are true for you, then let's move ahead!

A first example: derive_deftly(HelloWorld)

Here we'll make an example project with a simple derive-deftly template. Our template won't do much, but it will get us started using the system.

To begin with, we'll create a new project:

$ cargo init --lib dd-hello-world
$ cd dd-hello-world

Next, we'll add the derive-deftly crate as a new dependency:

Note: You could also add the dependency by adding a line like this to the [dependencies] section in your Cargo.toml:

derive-deftly = "0.10"

(You should replace 0.10 with the real latest version.)

Now we can edit src/lib.rs in our project to define and use a new template.

Writing our HelloWorld template

There are two parts to using derive-deftly: specifying templates that you can use to derive new features for your structs and enums and then applying those templates to your types.

To define a template, you use define_derive_deftly!, as in

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

define_derive_deftly! {
    HelloWorld:

    impl $ttype {
        pub fn greet() {
            println!("Greetings from {}", stringify!($ttype));
        }
    }
}
}

This is a very simple template: it uses a single expansion: $ttype. (We'll get into more expansions, and more useful examples, later on. For now, all you need to know is that $ttype expands to the type on which you're applying your template.)

Later on, you can apply HelloWorld to your own type with [#[derive(Deftly)]derive-Deftly, as in:

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

   impl $ttype {
       pub fn greet() {
           println!("Greetings from {}", stringify!($ttype));
       }
   }
}
use derive_deftly::Deftly;

#[derive(Clone, Debug, Deftly)]
#[derive_deftly(HelloWorld)]
pub struct MyStruct;

MyStruct::greet();
}

Limitations of our template

Our template won't work for every type! For example, suppose that we try to apply it to a generic type:

#[derive(Deftly)]
#[derive_deftly(HelloWorld)]
struct Pair<T> {
    first: T,
    second: T,
}

In response, derive-deftly will try to expand our template to something like this:

impl Pair {
    pub fn greet() {
        println!("Greetings from {}", stringify!(Pair));
    }
}

But that won't work in Rust. Instead, we would need a template to expand to something more like this:

impl<T> Pair<T> {
    ...
}

We'll talk about how to make templates work for types like this in later chapters.

What's next?

Next, we'll take a detour, and show how to expose our template so that it can be used in other modules, and other crates.

After that, we'll look over a more complex example, and learn more features of derive-deftly. We'll write a more useful template, and have it apply to more types.

Using templates from other crates and modules

In the previous section, we learned how to crate our first template. We used derive-deftly to define a template called HelloWorld that could print the name of a type.

Before we go any further, we'll discuss how to use that template from other modules within the same crate, and how to expose that template so that other crates can use it.

We'll also discuss a convenient way to define a template that only needs to be used once.

Templates inside modules

When you define a template, derive-deftly turns it into a definition of a macro_rules! macro. In the example above, that would be derive_deftly_template_HelloWorld.

Unfortunately, Rust's rules for scoping of macro_rules! macros are awkward and confusing. In general, there are two ways to expose a macro; we'll go over how to use each one for a derive-deftly template.

With path-scoped macros

With modern versions of Rust (Rust 2018 and later), you can use path-based scope to define the scope of a macro. With this method, you use pub use (or pub(crate) use, etc) to define the scope for a macro, and allow it to be accessed by its path.

pub mod hello_world {
    use derive_deftly::define_derive_deftly;
    define_derive_deftly! {
        HelloWorld: /* ... */
    }
    pub(crate) use derive_deftly_template_HelloWorld;
}

mod caller {
    use derive_deftly::Deftly;
    use super::hello_world::derive_deftly_template_HelloWorld;

    #[derive(Deftly)]
    #[derive_deftly(HelloWorld)]
    pub struct MyStruct;

    // Alternatively, you use the path, and avoid having to say "use":
    #[derive(Deftly)]
    #[derive_deftly(super::hello_world::HelloWorld)]
    pub struct MyOtherStruct;
}
fn main() {} // so that the above is not wrapped in fn main().

This is generally the better method to use.

Using #[macro_use]

We can also expose a macro using textual scope and macro_use: We prefix that module's mod statement with #[macro_use]. If you do this, the module defining the macro must come before the module where you use the macro. (Also, the macro's name is never scoped within the module.)

#![allow(unused)]
fn main() {
#[macro_use] // make HelloWorld visible outside the module (within the crate)
mod hello_world {
    use derive_deftly::define_derive_deftly;
    define_derive_deftly! {
        HelloWorld: /* ... */
    }
}
mod caller { // must come after mod hello_world
    use derive_deftly::Deftly;
    #[derive(Deftly)]
    #[derive_deftly(HelloWorld)] // not hello_world::HelloWorld
    pub struct MyStruct;
}
}

Exporting templates

But now suppose that you want to expose your template, so that people can use it from any crate.

To do this, you use export before the name of your macro:

pub trait HelloWorld {
    fn greet();
}

derive_deftly::define_derive_deftly! {
    /// Derives `HelloWorld`, providing `greet`
    export HelloWorld:
    impl $crate::HelloWorld for $ttype {
        fn greet() {
            println!("Greetings from {}", stringify!($ttype));
        }
    }
}
fn main() {}

Declaring our template as export HelloWorld in this way is equivalent to marking derive_deftly_template_HelloWorld with #[macro_export]. Doing this puts the template into the root of the crate. You can make the template visible elsewhere using pub use.

Note that this time, we've defined HelloWorld as a trait, and we've changed our template to refer to that trait as $crate::HelloWorld. The $crate syntax will expand to the name of the crate in which our template was defined, so that when later we expand this template in a different crate, it will find the right trait. (Otherwise, it would fail if the HelloWorld trait were not in scope.)

We've also added a doc comment, which will appear in the public API documentation for our crate.

Additionally, we need to re-export derive_deftly from our crate, so that when our template is applied to user types, a compatible derive-deftly driver is used. And we should also invoke derive_deftly::template_export_semver_check once, somewhere in our crate: this will tells us about any implications for your crate's semver, when you change your Cargo.toml to upgrade the derive-deftly crate.

// At the root of our crate:

#[doc(hidden)]
pub use derive_deftly;

// Use the current version of derive_deftly here:
derive_deftly::template_export_semver_check!("0.11.0");

Now, when somebody wants to use our template from a different crate, they can do it like this:

// Let's pretend our crate is called hello_world.
use hello_world::{
    // This is the trait we defined...
    HelloWorld,
    // This is the macro that makes our template work.
    // (We might come up with a better syntax for this later).
    derive_deftly_template_HelloWorld
};
use derive_deftly::Deftly;

#[derive(Deftly)]
#[derive_deftly(HelloWorld)]
struct TheirStructure {
    // ...
}

Note that exporting a template to other crates with export doesn't affect its visibility within your crate. You may still need #[macro_use].

What's next

Now that we've explained how to expose a template, it's time to learn about more features from derive-deftly. In the next session, we'll start working on a Clone example, and we'll learn how to work with structs, enums, fields, variants, and more.

If you're only deriving once...

If you want, you can apply a template to an existing type without having to name that template. You might want to do this if you have a template that you only want to apply to a single struct, and so you don't want to bother naming it.

Supposing that you wanted to apply the template above to MyStruct and MyStruct alone, you could have said:

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

#[derive(Clone, Debug, Deftly)]
#[derive_deftly_adhoc]
pub struct MyStruct;

derive_deftly_adhoc!{
    MyStruct:

    impl $ttype {
        pub fn print_name() {
            println!("The name of this type is {}", stringify!($ttype));
        }
    }
}
}

Of course, that's not so useful yet. In this case, it would have been easier just to write impl MyStruct { pub fn print_name() { ... } }. But soon, we'll see how to write more interesting templates, and how to use them to create much more interesting code.

The rest of this document will focus on how to use derive_deftly's template features to your fullest advantage. If you're the kind of person who wants to skip straight to the reference manual, you can find it over here.

Example 1: Writing your own Clone

In the previous chapters, we've learned how to define a simple derive-deftly template, and how to export it to the rest of the world.

In this chapter, we'll use derive-deftly to implement our own version of Rust's standard #[derive(Clone)] macro. At first, it will be very simple; later on, we'll add a few features that Rust's derive(Clone) doesn't have.

With these examples, we'll learn more feature of derive-deftly, including:

  • Writing templates that apply to structs and enums.
  • Writing templates that apply to generic types.
  • Using a variety of expansions, including ones for top-level types, variants, and fields.

Aside:

We've picked a simple trait to derive on purpose, so that we can focus on the features of derive-deftly without the additional complexity of introducing an unfamiliar trait as well.

Please let us know if this approach works for you! We're learning how to explain these concepts as we go along.

Working with structs and fields

Let's start by imagining that we had to write Clone from scratch for a simple structure like this:

#![allow(unused)]
fn main() {
struct GiftBasket {
   n_apples: u8,
   n_oranges: u8,
   given_from: Option<String>,
   given_to: String,
}
}

We'd probably write something like this:

#![allow(unused)]
fn main() {
struct GiftBasket {
  n_apples: u8,
  n_oranges: u8,
  given_from: Option<String>,
  given_to: String,
}
impl Clone for GiftBasket {
    fn clone(&self) -> Self {
        Self {
            n_apples: self.n_apples.clone(),
            n_oranges: self.n_oranges.clone(),
            given_from: self.given_from.clone(),
            given_to: self.given_to.clone()
        }
    }
}
}

(In reality, since n_apples and n_oranges both implement Copy, you wouldn't actually call clone() on them. But the compiler should be smart enough to optimize the clone() away.)

If you imagine generalizing this to any simple struct with named fields, you might come up with a pseudocode template like this one:

impl Clone for ⟪Your struct⟫ {
    fn clone(&self) -> Self {
        Self {
            for each field:
                ⟪field name⟫: self.⟪field name⟫.clone(),
        }
    }
}

And here's how that pseudocode translates into a derive-deftly template:

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

define_derive_deftly! {
    MyClone:

    impl Clone for $ttype {
        fn clone(&self) -> Self {
            Self {
                $( $fname : self.$fname.clone() , )
            }
        }
    }
}
}

Let's look at that template piece by piece. Certain marked "expansion keywords" in the contents (those starting with $) are replaced with a value that depends on the struct we are applying the template to.

You've already seen $ttype ("top-level type"): it expands to the type on which you are applying the macro. There are two new pieces of syntax here, though: $( ... ) and $fname.

In derive-deftly templates, $( ... ) denotes repetition: it repeats what is inside it an "appropriate" number of times. (We'll give a definition of "appropriate" later on.) Since we want to clone every field in our struct, we are repating the field: self.field.clone() , part of our implementation once for each field.

The $fname ("field name") expansion means "the name of the current field". Which field is that? Since $fname occurs inside $( ... ), we will repeat the body of the $( ... ) once for each field, and expand $fname to the name of a different field each time.

(Again, more advanced repetition is possible; there's more to come.)

An aside on naming

At this point, you've seen expansions with names like $ttype and $fname, and you may be wondering where these names come from. If you want to know more, you can skip ahead to the section on naming conventions.

Will MyClone apply to tuple and unit structs?

Rust defines several kinds of struct:

  • structs with fields like struct Foo {...};
  • tuple structs like struct Foo(...);
  • and unit structs like struct Foo;

So far, you've only applied your MyClone template to a struct with fields. But with a tuple struct, or a unit struct, you might expect it to fail.

Surprisingly, our template still works fine! This isn't because of any clever trickery from derive-deftly: it's just how Rust works. When you use MyClone on tuple or unit struct, it will expand to code like this the example below. And this syntax, though not the normal way to work with these types, is still valid Rust!

#![allow(unused)]
fn main() {
struct TupleStruct(String, Option<String>);
impl Clone for TupleStruct {
    fn clone(&self) -> Self {
        Self {
            0: self.0.clone(),
            1: self.1.clone(),
        }
    }
}

struct UnitStruct;
impl Clone for UnitStruct {
    fn clone(&self) -> Self {
        Self {
        }
    }
}
}

This will be a common theme when using derive-deftly: Rust often lets you use a slightly unidiomatic syntax, so that you can handle many different cases in the same way.

Making MyClone apply to enumerations

At this point, you've probably noticed that we've defined MyClone to apply to structs only, but it won't (yet) work on enums. Let's fix that!

Suppose that we have enumeration defined like this:

#![allow(unused)]
fn main() {
enum AllTypes {
    NoData,
    Tuple(u8, u16),
    Struct { a: String, b: String }
}
}

We want to make sure that MyClone can recognize and re-construct each of the three variants.

We can do that as follows:

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

    impl Clone for $ttype
    {
        fn clone(&self) -> Self {
            match self {
                $(
                    $vpat => $vtype {
                        $(
                            $fname: $fpatname.clone(),
                        )
                    },
                )
            }
        }
    }
}
}

Note that now we have two levels of nested repetition. First, we match once for each variant. (This is at the $vpat and $vtype level.) Then we match once for each field of each variant. (This is at the $fname and $fpatname level.)

Let's go over the new expansions here. First, we have $vpat ("variant pattern"): that expands to a pattern that can match and deconstruct a single variant. Then, we have $vtype ("variant type"): that's the type of the variant, suitable for use as a constructor. Then, inside the variant, we have $fname: that's our field name, which we've seen it before. Finally, we have $fpatname ("field pattern name"): that is the name of the variable that we used for this field in the pattern that deconstructed it.

When we apply MyClone to our enumeration, we get something like this:

#![allow(unused)]
fn main() {
enum AllTypes { NoData, Tuple(u8, u16), Struct { a: String, b: String } }
impl Clone for AllTypes {
    fn clone(&self) -> Self {
        match self {
            AllTypes::NoData {} => AllTypes::NoData {},
            AllTypes::Tuple {
                0: f_0,
                1: f_1,
            } => AllTypes::Tuple {
                0: f_0.clone(),
                1: f_1.clone()
            },
            AllTypes::Struct {
                a: f_a,
                b: f_b,
            } => AllTypes::Struct {
                a: f_a.clone(),
                b: f_b.clone()
            },
        }
    }
}
}

Note that our template above will still work fine on a regular struct, even though it's written for an enum. If we apply the version of MyClone above to struct Example { a: u8, b: String }, we get this:

#![allow(unused)]
fn main() {
struct Example { a: u8, b: String }
impl Clone for Example {
    fn clone(&self) -> Self {
        match self {
            Example {
                a: f_a,
                b: f_b,
            } => Example {
                a: f_a.clone(),
                b: f_b.clone(),
            }
        }
    }
}
}

So (in this case at least) we were able to write a single template expansion that worked for both structs and enum`s.

Making MyClone apply to generics

We've gotten a MyClone implementation that works with several kinds of struct struct, and with enums too. But here's a structure where our current MyClone implementation will fall flat:

#![allow(unused)]
fn main() {
use std::fmt::Debug;
struct MyItems<T:Clone, U>
    where U: Clone + Debug
{
    things: Vec<T>,
    items: Vec<U>
}
}

When we go to expand our template, it will generate something like:

impl Clone for MyItems { ... }

That isn't valid! For our expansion to compile, we would need to use the generic parameters and their "where" constraints, like so:

impl<T:Clone, U> Clone for MyItems<T,U>
    where U: Clone+Debug
{ ... }

To do this with derive-deftly, we have to expand our MyClone templatate as shown below:

Therefore, to get our MyClone template working with generic types, we can expand it to add the same parameters and constraints.

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

    // (The next three lines are new)
    impl<$tgens> Clone for $ttype
    where $twheres
    {
        // (The rest is as before...)
        fn clone(&self) -> Self {
            match self {
                $(
                    $vpat => $vtype {
                        $(
                            $fname: $fpatname.clone(),
                        )
                    },
                )
            }
        }
    }
}
use std::fmt::Debug;
#[derive(Deftly)]
#[derive_deftly(MyClone)]
struct MyItems<T:Clone, U>
   where U: Clone + Debug
{
    things: Vec<T>,
    items: Vec<U>
}

#[derive(Deftly)]
#[derive_deftly(MyClone)]
enum TestCase<A: Clone> { Variant(A) }
}

Here we meet two new keywords. $tgens ("top-level generics") becomes the generic parameters as declared on the top-level type. (In our case, that's T:Clone, U.) The $twheres keyword ("top-level where clauses") becomes the where constraints as declared on the top-level type. (In our case, that's U: Clone+Debug.)

Note that $ttype expands to the top-level type: that's now MyItems<T,U>, which is what we want in this case. If we had wanted only MyItems without <T,U>, we would say $tname instead.

But does the syntax work for non-parameterized types?

Will this template still work for non-parameterized types? Indeed, it will! To Rust, this syntax is perfectly fine:

#![allow(unused)]
fn main() {
struct Simple {
    a: String
}
// Note empty <> and "where" list:
impl<> Clone for Simple
where
{
    fn clone(&self) -> Self {
        Self {
            a: self.a.clone(),
        }
    }
}
}

Making MyClone apply conditionally

Now, for the first time, we will make MyClone do something that Rust's #[derive(Clone)] does not: it will apply only when the fields of a struct are Clone.

For example, suppose have a struct like this:

#![allow(unused)]
fn main() {
use std::sync::Arc;
struct Indirect<T>(Arc<T>, u16);
}

If you try to derive Clone on it, the compiler will generate code something like this:

impl<T: Clone> Clone for Indirect<T> { ... }

But that T: Clone constraint isn't strictly necessary: Arc<T> always implements Clone, so your struct could have been be Clone unconditionally.

But using derive-deftly, you can define a template that derives Clone only for the cases where the actual required constraints are met:

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

    impl<$tgens> Clone for $ttype
    where $twheres
          // This is the new part:
          $( $ftype : Clone , )
    {
        // (The rest is as before...)
        fn clone(&self) -> Self {
            match self {
                $(
                    $vpat => $vtype {
                        $(
                            $fname: $fpatname.clone(),
                        )
                    },
                )
            }
        }
    }
}
use std::{sync::Arc, fmt::Debug} ;
#[derive(Deftly)]
#[derive_deftly(MyClone[dbg])]
struct Example<T> where T: Debug {
  arc: Arc<T>
}
#[derive(Deftly)]
#[derive_deftly(MyClone[dbg])]
struct Example2<T> {
  arc: Arc<T>
}
}

Here, we are using $ftype. ("field type") to get the actual type of each field. Since we're repeating it with $( ... ), we are requiring every field to be Clone.

Will this work with non-generic fields, or if the same field is used more than once? Once again, yes! To Rust, this is a perfectly valid example:

impl<T> Clone for Direct
where
    T: Clone,
    T: Clone,
    String: Clone
{
    ...
}

What about that comma?

If you're paying close attention, you might have thought we had a syntax error above when we didn't use an explicit comma after where $twheres.

This time, derive_deftly has exactly one piece of cleverness at work. It makes sure that either $twheres is empty, or that it ends with a comma. That way, when your template expands where $twheres $( $ftype : Clone , ) it won't produce where U: Debug + Clone T: Clone(which is a syntax error) orwhere ,` (which is also a syntax error).

A further note on repetition

Note that when we define our additional where clauses above, we said where $( $ftype: Clone, ) at the top level. We didn't have to specify separate of repetition for variants and fields: if we have only $ftype in a top-level repetition, derive_deftly will iterate over all fields in all variants.

Sometimes, if you do something subtle, derive-deftly may not be able to figure out what you're trying to repeat over. You can use ${for fields {...}} or ${for variants {...}} to specify explicitly what you want to repeat. So, above, instead, we could have written

#![allow(unused)]
fn main() {
use derive_deftly::{Deftly, derive_deftly_adhoc};
#[derive(Deftly)]
#[derive_deftly_adhoc]
enum TestCase<A> { Variant(A) }
derive_deftly_adhoc! { TestCase:
    fn testcase<$tgens>() where
${for fields { $ftype: Clone, }}
    {}
}
}

You can also use ${for ...} rather than $(...) in cases where you feel it makes your macro code clearer. For example, we could have used ${for} to write our MyClone example more explicitly like this:

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

    impl<$tgens> Clone for $ttype
    where $twheres
          ${for fields { $ftype: Clone , }}
    {
        fn clone(&self) -> Self {
            match self {
                ${for variants {
                    $vpat => $vtype {
                        ${for fields {
                            $fname: $fpatname.clone(),
                        }}
                    },
                }}
            }
        }
    }
}
}

Some more advanced topics

Now that we've our first basic example under our belt, let's look at some other things that derive-deftly can do.

Transforming names and strings

Often, it's useful to define new identifiers based on existing ones, or to convert identifiers into strings.

You could use the existing paste crate for this, or you can use a native facility provided by derive-deftly.

For example, suppose that you want to define a template that makes a "discriminant" type for your enumerations. You want the new type to be named FooDiscriminant, where Foo is the name of your existing type. While you're at it, you want to add an is_ function to detect each variant.

You can do that like this:

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

    #[derive(Copy,Clone,Eq,PartialEq,Debug)]
    enum ${paste $tname Discriminant} {
        $(
            $vname,
        )
    }

    impl<$tgens> $ttype where $twheres {
       fn discriminant(&self) -> ${paste $tname Discriminant} {
          match self {
              $(
                  $vpat => ${paste $tname Discriminant}::$vname,
              )
          }
        }

        $(
            fn ${paste is_ ${snake_case $vname}} (&self) -> bool {
                self.discriminant() ==
                    ${paste $tname Discriminant} ::$vname
            }
        )
    }
}
}

Here we see a couple of new constructs.

First, we're using ${paste} to glue several identifiers together into one. When we say ${paste $tname Discriminant}, we are generating a new identifier from $tname (the type name) and the word Discriminant. So if the type name is Foo, the new type will be called FooDiscriminant.

Second, we're using ${snake_case} to transform an identifier into snake_case (that is, lowercase words separated by underscores). We use this to turn the name of each variant ($vname) into a name suitable for use in a function name. So if a variant is called ExampleVariant, ${snake_case $vname} will be example_variant, and ${paste is_ ${snake_case $vname}} will be is_example_variant.

There are other case-changers:

  • ${pascal_case my_ident} becomes MyIdent. You can also write this as ${upper_camel_case ..}.
  • ${lower_camel_case my_ident} becomes myIdent.
  • ${shouty_snake_case MyIdent} becomes MY_IDENT.
  • ${snake_case MyIdent} becomes my_ident, as you've already seen.

You can abbreviate ${paste ...} as $<...>.

Pasting onto types

Here's a side-note: You can use ${paste} to append identifiers to a type (like $ttype or $ftype), not just an identifier. When you do this, ${paste} will do the right thing even if the type is generic.

For example, if $ftype is Vec<u8>, then ${paste $ftype Builder} will expand to VecBuilder<u8>, not Vec<u8>Builder.

A note on syntax

In this last section, you've seen a new syntax for the first time. Both ${paste ident ident..} and ${snake_case ident} are special cases of the following meta-syntax, which derive-deftly uses everywhere:

${KEYWORD ARGS.. }

In fact, if you want, you can use this format for all of the expansion macros you have already seen: $ttype is just a shortened form for ${ttype}, $fname is just ${fname}, and so on.

Some keywords, including some of those we've already seen, can take named arguments. The syntax for this is:

${KEYWORD ARGNAME=VALUE ARGNAME=VALUE...}

For example, we can use this syntax to give optional arguments to $vpat; see the template syntax reference for more information.

If you ever need to write a literal $ (say, if you want to confuse yourself by making derive-deftly-generated pattern macros) you can write $$.

Naming conventions for derive-deftly expansions

Here we discuss some naming conventions used in the naming of derive-deftly expansions.

Many derive-deftly expansions' names start with a single letter to indicate what information they use:

  • t for top-level (the struct, enum, or union you are applying the template to),
  • v for variant (a variant of an enum),
  • or f for field (a single field of a struct or variant).

For example, there is $ttype for "top-level type" and $fname for "field name".

(We say "top-level" instead of "struct", since the "top-level" part of a declaration can also be an enum or a union.)

Many derive-deftly expansions end with a short identifier for what they contain. For example, $tname is the name of a top-level type, $vname is the name of a variant, and $fname is the name of a field. Whenever possible (and logical), we have tried to use the same identifier for the t, v, and f cases.

Example 2: Defining a constructor function

In this section, we'll be using another example to demonstrate more of what derive-deftly can do.

We'll be building a Constructor template to define a new() function for a struct, without having to write out all of its arguments.

Let's start with the following (struct-only) template:

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

   impl<$tgens> $ttype where $twheres {
      pub fn new( $( $fname: $ftype , ) ) -> Self {
          Self {
              $( $fname , )
          }
      }
   }
}
}

When you apply the above template to a type like this:

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

  impl<$tgens> $ttype where $twheres {
      pub fn new( $( $fname: $ftype , ) ) -> Self {
          Self {
              $( $fname , )
          }
      }
  }
}
use derive_deftly::Deftly;
#[derive(Deftly)]
#[derive_deftly(Constructor)]
struct Ex<A> {
  a: f64,
  b: A,
  c: String
}
}

You'll get a constructor like this:

#![allow(unused)]
fn main() {
struct Ex<A> { a: f64, b: A, c: String }
impl<A> Ex<A> {
    pub fn new( a: f64, b: A, c: String ) -> Self {
        Self { a, b, c }
    }
}
}

So far, there aren't any new techniques at work here. We'll add some more down below.

Limiting a template to one kind of type.

The Constructor template above doesn't work for enumerations. If you try to apply it to one, you'll get a not-entirely-helpful error message.

In earlier examples, we've shown how to make templates that apply to enums as well as structs. But let's suppose that in this case, we want our template to be struct-only.

We can tell derive-deftly about this restriction, to help it generate more useful error messages:

#![allow(unused)]
fn main() {
use derive_deftly::define_derive_deftly;
define_derive_deftly! {
   Constructor for struct: // (1)

   // The rest is unchanged
   impl<$tgens> $ttype where $twheres {
      pub fn new( $( $fname: $ftype , ) ) -> Self {
          Self {
              $( $fname , )
          }
      }
   }
}
}

(Note the use of for struct above at // (1).)

Now if we try to apply our template to an enum, we'll get a more useful error:

error: template defined for struct, but applied to enum

Debugging templates

When writing complex templates, it can sometimes be hard to figure out compiler errors. derive-deftly has some features which can help:

Syntax checking the expansion

You can use the expect option to tell derive-deftly what your macro is supposed to produce:

#![allow(unused)]
fn main() {
use derive_deftly::{Deftly, define_derive_deftly};
define_derive_deftly! {
   Constructor for struct, expect items:

   impl<$tgens> $ttype where $twheres {
      // ...
   }
}
#[derive(Deftly)]
#[derive_deftly(Constructor)]
struct Test;
}

If the expansion fails to syntax check, you'll get not only an error pointing at the part of the template or structure which seems wrong, but also error messages pointing into a copy of the actual expansion. Hopefully you'll be able to see what's wrong there.

If your macro is supposed to expand to an expression, you can write expect expr instead of expect items.

Alternatively, you can request this syntax check when you invoke the macro:

#![allow(unused)]
fn main() {
use derive_deftly::{Deftly, define_derive_deftly};
define_derive_deftly! { Constructor: }
#[derive(Deftly)]
#[derive_deftly(Constructor[expect items])]
struct MyStruct { /* ... */ }
}

Note: The above section isn't really logical: "expect items" is always the case for templates defined with define_derive_deftly: only for adhoc templates is expect expr reasonable.

TODO: Rewrite and move this section.

Seeing a copy of the expansion

Sometimes, the expansion is syntactically valid, but is wrong in some other way.

You can use the dbg option to tell derive-deftly to print a copy of the expansion.

#![allow(unused)]
fn main() {
use derive_deftly::{Deftly, define_derive_deftly};
define_derive_deftly! { Constructor: }
#[derive(Deftly)]
#[derive_deftly(Constructor[dbg])]
struct MyStruct { /* ... */ }
}

You'll see the compiler print something like this:

---------- derive-deftly expansion of Constructor for MyStruct (start) ----------
impl<> MyStruct where {
    pub fn new(...) { ... }
}
---------- derive-deftly expansion of Constructor for MyStruct (end) ----------

Just as with the dbg! macro, you don't want to leave this in your production code.

Working with visibility

Our Constructor template above doesn't really make sense if it's applied to a non-public type; It would define the new() function as public, when the type is not meant to be exposed outside the crate. (Rust may even complain that we're declaring a public function that returns a private type!)

Let's fix this, and have our template give our constructor the same visibility as the type itself:

#![allow(unused)]
fn main() {
use derive_deftly::define_derive_deftly;
define_derive_deftly! {
   Constructor for struct:

   impl<$tgens> $ttype where $twheres {
      // (this "$tvis" is new)
      $tvis fn new( $( $fname: $ftype , ) ) -> Self {
          Self {
              $( $fname , )
          }
      }
   }
}
}

Here instead of saying pub fn new, we said $tvis fn new. The $tvis keyword will expand to the visibility of the top-level type.

There is a similar keyword $fvis that expands to the visibility of the current field.

(Since enum variants are always visible, there is no such keyword as $vvis. Since enum fields are always visible, $fvis in an enum always expands to pub.)

Using attributes to make a template take arguments

Let's suppose we want to make our Constructor template a little more flexible: we'd like to be able to give the new function a different name.

The usual way to pass an option to a derive macro is with an attribute, something like this:

#[derive(Deftly)]
#[derive_deftly(Constructor)]
#[deftly(constructor(newfn="function_name"))]
struct Example(...);

We can extend our template to take a function name, like this:

#![allow(unused)]
fn main() {
use derive_deftly::define_derive_deftly;
define_derive_deftly! {
   Constructor for struct:

   impl<$tgens> $ttype where $twheres {
      $tvis fn ${tmeta(constructor(newfn)) as ident} // (1)
      ( $( $fname: $ftype , ) ) -> Self {
          Self {
              $( $fname , )
          }
      }
   }
}

use derive_deftly::Deftly;
#[derive(Deftly)]
#[derive_deftly(Constructor)]
#[deftly(constructor(newfn="construct_example"))]
struct Example {
    a: f64,
    b: String
}
}

Here, instead of specifying "new" for the method name in our template, we give the name as ${tmeta(constructor(newfn))} as ident. This tells the template to look for an #[deftly(constructor(newfn="..."))] attribute on the type, to interpret that attribute as an identifier, and to use the value of that attribute in place of the keyword.

The as ident portion is mandatory; without it, derive-deftly can't tell how to parse the argument to the argument. Instead of ident, you can describe other kinds syntactical types, such as str, ty, path, or expr. See the reference for a complete list.

Note that we explicitly named our attribute as constructor(newfn). We could instead have specified it as newfn, but that would have the potential to conflict with attributes interpreted by other templates. As a best practice, if we expect our template to be widely used, we should namespace our attributes as in the example above.

The $tmeta ("toplevel meta") keyword that we used here tells the template to look at the #[deftly] attributes for the type. We can, instead, use $vmeta ("variant meta") to look for #[deftly] attributes for the current variant, or $fmeta ("field meta") to to look for #[deftly] attributes for the current field.

(We'll see an example of using $fmeta in a little while.)

Getting started with if/then/else conditionals

In the example above, we made it possible to rename the "new" function generated by our template. But our approach is flawed: if the user doesn't provide the #[deftly(newfn)] attribute, the template won't make a function name at all!

Let's show how to fix that:

#![allow(unused)]
fn main() {
use derive_deftly::define_derive_deftly;
define_derive_deftly! {
   Constructor for struct:

   impl<$tgens> $ttype where $twheres {
   $tvis fn
        // (1) This "if" defines the function name:
        ${if tmeta(constructor(newfn)) {
            ${tmeta(constructor(newfn)) as ident}
          } else {
            new
          }
        }
        // The rest is as before:
     ( $( $fname: $ftype , ) ) -> Self {
          Self {
              $( $fname , )
          }
      }
   }
}

use derive_deftly::Deftly;
#[derive(Deftly)]
#[derive_deftly(Constructor)]
#[deftly(constructor(newfn="construct_example"))]
struct Example {
   a: f64,
   b: String
}
#[derive(Deftly)]
#[derive_deftly(Constructor)]
struct Example2 {
   a: f64,
   b: String
}
}

Have a look at the part of the template marked with // (1). It introduces a new concept: conditional expansion. The ${if cond {expansion}} keyword checks whether a given condition is true. If it is, then the $if keyword expands to the expansion second argument. Otherwise, it expands to an "else" argument (if any).

Also, you can chain $ifs, as in ${if COND1 { ... } else if COND2 { ... } else { ... } and so on!

Here, the condition is tmeta(constructor(newfn)) ("toplevel metavalue"). That condition is true if the current type has an #[deftly(newfn)] attribute, and false otherwise. There are also vmeta and fmeta conditions to detect #[deftly(..)] attributes on variants and fields respectively.

Note: Don't confuse conditions with expansions! As we use it here, tmeta(x) is a condition (true or false), whereas ${tmeta(x) as ident} is an expansion.

Even though the names are similar in this case, conditions and expansions have separate namespaces; you cannot (in general) use them interchangeably.

There is a complete list of recognized conditions in the reference.

Multiple exclusive options with $select1

Sometimes you want to make sure exactly one condition matches.

This is a good choice when you have multiple options for controlling some feature, and you want to make sure that the user has specified exactly one (or maybe, no more than one).

You can do this using the ${select1} keyword:

${select1 COND1 { ... } else if COND2 { ... } ... else { ... }

Its syntax is nearly the same as ${if}, except that all conditions are checked. If more than one condition is true, then derive-deftly rejects ${select1}: it fails to expand, and emits an error. If no conditions are true, and there is no else clause, then ${select1} is also rejected (it fails to expand, and emits an error).

Here's an example of how we could use ${select1} if we wanted to allow constructor(default_name), as a synonym for our default behavior. In this case, we enforce that the user has specified constructor(newfn(X)) or constructor(default_name) or neither, but not both:

#![allow(unused)]
fn main() {
use derive_deftly::define_derive_deftly;
define_derive_deftly! {
  Constructor for struct:

  impl<$tgens> $ttype where $twheres {
pub fn
   ${select1
       tmeta(constructor(newfn)) { ${tmeta(constructor(newfn))} }
       else if tmeta(constructor(default_name)) { new }
       else { new }
    }
() {}
  }
}
}

Making Constructor set fields with Default

Sometimes, we'd like to make a template template that behave in different ways for different fields. For example, let's suppose that we want our Constructor template to be able to set fields to their default values, and not take them as arguments.

We can do this with an explicit conditional for each field:

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

   impl<$tgens> $ttype where $twheres {
     $tvis fn
        // This is the function name, same as before.
        ${if tmeta(constructor(newfn)) {
            ${tmeta(constructor(newfn)) as ident}
          } else {
            new
          }
        }
     (
       // These are the function arguments:
       $(
          ${when not(fmeta(constructor(default))) } // (1)
          $fname: $ftype ,
        )
     ) -> Self {
          Self {
              $( $fname:
                  ${if fmeta(constructor(default)) {
                    ::std::default::Default::default() // (2)
                  } else { $fname } }
                 , )
          }
      }
   }
}

use derive_deftly::Deftly;
#[derive(Deftly)]
#[derive_deftly(Constructor)]
struct Foo {
    #[deftly(constructor(default))]
    s: Vec<String>,
    n: u32,
}
}

Here we're using a new construct: $when. It's only valid inside a loop like $( ... ) or ${for ...}. It causes the output of the loop to be suppressed whenever the condition is not true.

The condition in this cases is not(fmeta(constructor(default))). (See // (1).) You've seen fmeta before; it's true when a given attribute is present on the current field. The not condition is just how we express negation. All together, this $when keyword causes each field that has #[deftly(Constructor(default))] applied to it to be omitted from the list of arguments to the new() function.

Note at // (2) that we're using ::std::default::Default::default(), rather than calling Default::default() unconditionally. Remember, macros expand at the position where they are invoked, and it's possible that we'll be invoked in a module that has been built without the standard prelude.

Besides $not, you can use other boolean operators in conditions too: there is an any(...) that is true whenever at least one of its arguments is true, and an all(...) that is true when all of its arguments are true.

Managing complexity with $define

Our constructor example has gotten fairly hard to read! Before we take it any further, let's refactor it to make it more clear what it's doing.

Here, we will use the $define and $defcond keywords to define new aliases for some of our longer expansions and conditions. This will make our intent more clear, and hopefully easier to read.

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

   // First, we define some aliases.

   // (1)
   ${define FUNC_NAME {
        ${if tmeta(constructor(newfn)) {
            ${tmeta(constructor(newfn)) as ident}
        } else {
            new
        }}
    }}

   // (2)
   ${defcond F_DFLT fmeta(constructor(default))}

   // (3)
   ${define F_INITIALIZER
       ${if F_DFLT {
          ::std::default::Default::default()
       } else {
          $fname
       }}
   }

  // Now that's out of the way, here is the template expansion:
  impl<$tgens> $ttype where $twheres {
     $tvis fn $FUNC_NAME(
       $(
          ${when not(F_DFLT)}  $fname: $ftype ,
        )
     ) -> Self {
          Self {
              $( $fname: $F_INITIALIZER , )
          }
      }
   }
}

use derive_deftly::Deftly;
#[derive(Deftly)]
#[derive_deftly(Constructor)]
struct Foo {
    #[deftly(constructor(default))]
    s: Vec<String>,
    n: u32,
}
}

In the example above, we've introduced some aliases for our trickier expressions, so that our template is easier to read.

At // (1) and // (3), we've used $define to introduce new expansions. The earlier one ($FUNC_NAME) is the name of our function; the later one ($F_INITIALIZER) is the expression that we use to initialize the current field.

At // (2), we've used $defcond to introduce a new condition, called $F_DFLT. It is true whenever we have been told to use the Default value for a the current field.

The $defcond and $define keywords don't expand to anything themselves; their only affect is to create the interpretation of the keywords or conditions they define.

NOTE: To avoid name conflicts, aliases created by $define and $defcond must be in ALL_CAPS.

As a general practice, it's a good idea to prefix field-specific aliases with F_ and variant-specific aliases with V_.

How does the scoping work here?

You might have noted that, in our example above, we define our new keywords and conditionals at the start of our template. In this context, there is no "current field"— but two of our definitions use fmeta, which requires the existence of a current field. How does this work?

In brief: the body of a definition is interpreted at the point where it is used. No matter where we put the ${define F_INITIALZER ...} in our template, it will use the value of ${fmeta ...} from the point where we actually use $F_INITIALIZER.

Example 3: Tracking the difference between objects

So far we've seen some of derive-deftly's features for defining different kinds of templates.

In this section, we'll extend on what we've learned already to build a template to track the difference between objects. Unlike our previous examples, this template will define a new "difference" type that mirrors the structure of the type we're applying it to.

Here's our motivating example. Let's suppose we have some complex structures and we want to expose the differences between those structures in a human-readable way. For example, we might want to write an assert_eq! test that shows only the actual changed fields between two instances, rather than showing their entire Debug outputs.

This example is inspired by the comparable crate, which has a bunch of other useful features we won't be replicating here. If this code seems like something you'd want, you should probably use comparable instead.

To begin with, let's assume our crate has defined a Diff trait, looking something like:

#![allow(unused)]
fn main() {
pub trait Diff {
    /// A type that describes the difference between
    /// two values of this type.
    type Difference: std::fmt::Debug;

    fn diff(&self, other: &Self) -> Option<Self::Difference>;
}

macro_rules! assert_no_difference {
    { $a:expr, $b:expr } => {
        match $a.diff($b) {
            Some(diff) => panic!("{} != {}: {:?}",
                stringify!($a),
                stringify!($b),
                diff),
            None => {},
        }
    }
}
}

We'll also assume that we've implemented our Diff trait on a wide array of types from std.

What kind of behavior do we want here? When we write a template to derive Diff on a struct MyStruct, we'll want our template to define a new struct whose fields represent the changes between the old structure and the new.

#![allow(unused)]
fn main() {
pub trait Diff { type Difference: std::fmt::Debug; }
impl Diff for String { type Difference = (); }
struct SomeOtherStruct;
impl Diff for SomeOtherStruct { type Difference = (); }
// Example struct
struct MyStruct {
    a: String,
    pub b: SomeOtherStruct
}

// Expected output
#[derive(Debug)]
struct MyStructDiff {
    a: Option< <String as Diff>::Difference >,
    // Let's assume we want the same visibility.
    pub b: Option< <SomeOtherStruct as Diff>::Difference >,
}
}

And when we write a template to derive Diff on an enum MyEnum, we'll want our template to define a new enumeration representing the change.

#![allow(unused)]
fn main() {
pub trait Diff { type Difference: std::fmt::Debug; }
impl Diff for String { type Difference = (); }
impl Diff for u32 { type Difference = (); }
#[derive(Debug,Clone)] struct SomeOtherStruct;
impl Diff for SomeOtherStruct { type Difference = (); }
// Example enum
#[derive(Clone, Debug)]
enum MyEnum {
    A,
    B(String, String),
    C {
        v1: u32,
        v2: SomeOtherStruct,
    }
}

// Expected output
#[derive(Debug)]
enum MyEnumDiff {
    // We'll never actually generate this variant:
    // if two values are both `MyEnum::A`, they have no difference.
    BothA,

    /// Both items are MyEnum::B, but some value changed.
    BothB(
        Option< <String as Diff>::Difference >,
        Option< <String as Diff>::Difference >,
    ),

    // Both items are  MyEnum::C, but some value changed.
    BothC {
        v1: Option< <u32 as Diff>::Difference >,
        v2: Option< <SomeOtherStruct as Diff>::Difference >,
    },

    // The two items are different variants of MyEnum.
    //
    // (This assumes that MyEnum implements Debug and Clone.)
    TypeChanged {
        original_value: MyEnum,
        new_value: MyEnum,
    }
}
}

Let's try to see how we can make this happen!

Some difficulties when creating our Diff type

In the last section, we saw what we want our derive_deftly(Diff) template to do. Here we'll analyze a few problems that we need to solve in order to make it work.

These problems mostly derive from the fact that we're trying to write a single template that can either generate a struct or an enum, depending on the type we're applying it to, but they all take different forms.

Problem 1: What keyword do we use to define?

We want an expansion that gives us struct for a struct and enum for an enum. While we could trivially build one with ${if is_struct {struct} else {enum}}, it seems kind of verbose.

Problem 2: How do we declare the generics?

When we define our new FooDiff struct, we want it to have the same generics that were used when we declared Foo.

We might think of doing this with something like:

struct $<$tname Diff><$tgens> where $twheres { ... }

And that might be good enough for many purposes, but it isn't quite right. Here's why: If Foo is defined as struct Foo<L:Display=Bar>, then FooDiff will only get defined as struct Foo<L:Display>. Thus the corresponding Diff type for Foo will not be FooDiff, but rather FooDiff<Bar>.

We need a way to declare generics along with their defaults.

Problem 3: Extra braces on the enum case.

For the enum case, we need our template to generate:

enum FooDiff { Variant { ... }, Variant { ... }, ... }

But for the struct case, we want:

struct FooDiff { ... }

Note that there are extra braces in the enum case. How can we generate those?

(We can't use ${if} to insert a single brace, since all the arguments to a derive-deftly expansion need to have balanced braces.)

Problem 4: Defining our structs and variants

Earlier we saw that when constructure and destructing, we could pretend that tuple structs and tuple variants were really braced variants with members named 0, 1, 2, and so on; and that we could pretend that unit structs and unit variants were just empty braced structs.

But this approach doesn't work when declaring a struct or variant: struct Foo {} is not the same as struct Foo;, and struct Foo { 0: u32 } is a syntax error!

So we will need a different approach. We'll need to generate braces and field names if the struct/variant has named fields; we'll need to generate parentheses and no field names if the struct/variant is a tuple; and we'll need to generate nothing at all if we have a unit struct/variant.

(Moreover, if we're generating a tuple struct or a unit struct, we'll need a trailing semicolon, but if we're generating any kind of a variant, we'll need a trailing comma.)

Problem 5: field visibility works differently among structs and enums

We decided above that we wanted each "difference" field to have the same visibility as the original field in our struct and enum.

Earlier, we saw $fvis as a way to get the visibility of a field. But this won't work when we're declaring an enum: since every enum field is public, $fvis for an enum's fields always expands to pub, and Rust won't allow us to write

enum FooDiff {
    Variant { pub a: u32; },
}

A brute-force approach for applying to structs and enums

One way we can solve our problems with writing a derive_deftly(Diff) template is a brute-force approach. Here we just use conditionals to write different versions of our template for the struct case and the enum case. To handle the difference between tuple and unit structs and variants, we can use conditionals as well.

Here are the new conditions we'll need:

  • is_enum - True if the top-level type is an enum.
  • is_struct - True if the top-level type is a struct.
  • v_is_unit - True if the current variant (or the top-level type, in the case of a struct) is a unit struct/variant.
  • v_is_tuple - True if the current variant (or the top-level type, in the case of a struct) is a tuple struct/variant.
  • v_is_named - True if the current variant (or the top-level type, in the case of a struct) is a struct/variant with named fields.

With these conditions, we can write our template, albeit with quite a lot of redundancy.

Note 1: You may want to refresh your memory about ${select1} and $<pasting>.

Note 2: We're ignoring generics in this example, for clarity.

#![allow(unused)]
fn main() {
pub trait Diff { type Difference: std::fmt::Debug; }
impl Diff for u32 { type Difference = (); }
use derive_deftly::{define_derive_deftly, Deftly};
define_derive_deftly!{
    Diff:

    ${define DIFF_TYPE
        { Option< <$ftype as Diff>:: Difference> }
    }

    ${select1 is_struct {
        ${select1 v_is_unit {
            #[derive(Debug)]
            $tvis struct $<$tname Diff>;
        } else if v_is_tuple {
            #[derive(Debug)]
            $tvis struct $<$tname Diff>(
                ${for fields {$DIFF_TYPE , }}
            );
        } else if v_is_named {
            #[derive(Debug)]
            $tvis struct $<$tname Diff> {
                $(
                    $fvis $fname: $DIFF_TYPE,
                )
            }
        }} // end select1
    } else if is_enum {
        #[derive(Debug)]
        $tvis enum $<$tname Diff> {
            $(
                ${select1 v_is_unit {
                    // nothing to do; we don't emit anything here.
                } else if v_is_tuple {
                    $<Both $vname>(
                        ${for fields {$DIFF_TYPE , }}
                    ),
                } else if v_is_named {
                    $<Both $vname> {
                        $(
                            $fname: $DIFF_TYPE,
                        )
                    },
                }} // end select1
            ) // end iteration over variants

            TypeChanged {
                original_value: $ttype,
                new_value: $ttype,
            },
        }
    }} // end select1
    impl Diff for $tname {
        type Difference = $<$tname Diff>;

        // ... and at this point, you still have to define
        // an implementation for `fn diff()`: good luck!
    }
}
use derive_deftly_template_Diff;
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Unit;
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Tuple(u32, u32);
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Named { a: u32, b: u32 }
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
enum Enum { Un, Tup(u32,u32), Nam { a: u32, b: u32 } }
}

This is a viable approach, but not a very maintanable one: look how we've had to copy out our definition separately for every possible Rust syntax! We managed to save some repetition with $define, but we can still do better.

A better solution to our problems with Diff

In previous sections, we've seen a few problems with writing a template that can define either a struct or an enum. We built a brute-force solution that was workable if not especially elegant. Here, we'll show a better way.

We'll start by revisiting those problems; this time we'll show a better solution to each one. After we've gone over those solutions, we'll put them together into a working template.

Revisiting our problems

So, how can we make our template?

Solving problem 1: $tdefkwd for a top-level keyword

We needed a way to get the right keyword for our new type. This is available nicely as $tdefkwd ("toplevel definition keyword"), which expands to struct, union, or enum as appropriate.

Solving problem 2: $tdefgens for generics with defaults

We needed a way to declare generics along with their defaults. This come in the form of $tdefgens, which gives the generics from the top-level type along with their bounds and their defaults.

Solving problem 3: $tdefvariants to brace our variants as needed

We needed some way to wrap our enum variants in braces, but leave our structs unwrapped.

Derive-deftly solves this problem with the $tdefvariants expansion, which adds braces only when the top-level type is an enum. In other words, ${tdefvariance ARG} expands to {ARG} for an enum, but ARG otherwise.

Solving problem 4: $vdefbody and $fdefine for struct/variant types

We needed some way to make approprite type and field declarations no matter whether the current struct or variant is a unit struct/variant, a tuple struct/variant, or a struct with named fields/fields.

We solve this problem in two parts.

First, the $vdefbody expansion gives us the appropriate syntax to declare a variant or structure corresponding to the current variant/structure, with the necessary braces, parentheses, and/or trailing semicolon or comma.

Specifically, ${vdefbody VNAME FIELDS} will expand to one of:

  • FIELDS; for unit structs (though typically FIELDS will be empty)
  • (FIELDS); for tuple structs
  • {FIELDS} for structs with named fields
  • VNAME FIELDS, for a unit variant in an enum (though typically FIELDS will be empty)
  • VNAME(FIELDS), for a tuple variant in an enum
  • VNAME { FIELDS }, for a variant with named fields in an enum.

Second, to provide a field name and colon if appropriate, we use ${fdefine NAME}, which expands to NAME: if fields are named, and to nothing otherwise.

Solving problem 5: $fdefvis for visibility

We needed some way to define appropriate visibility for struct fields, given that pub isn't allowed to appear in an enum definition.

For this, we use $fdefvis, which expands to the visibility of the current field in a struct, and to nothing in an enum.

Putting it all together

This probably sounds like a lot, and admittedly it can be tricky to synthesize.

As a template, it's handy to start by modifying this syntax, which just reproduces the original type with a new name.

$tvis $tdefkwd $<$tname Copy><$tdefgens>
${tdefvariants $(
    ${vdefbody $vname $(
        $fdefvis ${fdefine $fname} $ftype,
    ) }
) }

Based on that, we produce the following. (As an added bonus, we'll define the actual diff method!)

#![allow(unused)]
fn main() {
pub trait Diff { type Difference: std::fmt::Debug; fn diff(&self, o:&Self) -> Option<Self::Difference>; }
impl Diff for u32 { type Difference = (); fn diff(&self,o:&Self) -> Option<()> {None} }
use derive_deftly::{define_derive_deftly, Deftly};
define_derive_deftly!{
    Diff:

    ${if is_union {${error "Unions aren't supported"}}}

    ${define DIFF_TYPE
        { Option< <$ftype as Diff>:: Difference> }
    }

    #[derive(Debug)]
    $tvis $tdefkwd $<$tname Diff><$tdefgens>
    ${tdefvariants
        $(
            ${vdefbody $<Both $vname>
                $(
                    $fdefvis ${fdefine $fname} $DIFF_TYPE ,
                )
            }
        )
        ${if is_enum {
            TypeChanged {
                original_value: $ttype,
                new_value: $ttype,
            },
        }}
    }

    impl<$tgens> $<$ttype Diff> where $twheres {
        /// Helper: Return this value if it reflects a real change.
        fn if_is_a_change(self) -> Option<Self> {
            match &self {
                $(
                    // (1)
                    ${vpat self=$<$ttype Diff> vname=$<Both $vname>} => {
                        if $( $fpatname.is_none() && ) true  {
                            return None;
                        }
                    }
                )
                ${if is_enum { Self::TypeChanged{..} => {} }}
            }
            return Some(self);
        }
    }

    impl<$tgens> Diff for $ttype where
        ${if is_enum {$ttype: Clone,}}
        $twheres
    {
        type Difference = $<$ttype Diff>;
        fn diff(&self, other: &Self) -> Option<Self::Difference> {
            match (self, other) {
                $(
                    // (2)
                    ($vpat, ${vpat fprefix=other_}) => {
                        // (3)
                        ${vtype self=$<$ttype Diff> vname=$<Both $vname>} {
                            $(
                                $fname : Diff::diff($fpatname, $<other_ $fname>),
                            )
                        }
                    }
                ) // End variants.
                ${if is_enum {
                    (original_value, new_value) => {
                        Self::Difference::TypeChanged {
                            original_value: original_value.clone(),
                            new_value: new_value.clone(),
                        }
                    }
                }}
            }.if_is_a_change()
        }
    }
}
use derive_deftly_template_Diff;
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Unit;
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Tuple(u32, u32);
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Named { a: u32, b: u32 }
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
enum Enum { Un, Tup(u32,u32), Nam { a: u32, b: u32 } }
}

With understanding, this approach should appear much terser and more logical than the many-cases approach from the last section.

We've introduced a few previously unseen expansions: let's talk about them now.

At the beginning, we use $error. If it is ever expanded, then the whole template expansion is rejected with a compile-time error. We use it to make sure that nobody tries to apply our template to a union.

We use $vpat in a new form. As discussed in the reference, $vpat can take arguments to change its behavior. We use this feature twice:

  • At // (1) we use the self argument to make our pattern destructure the Diff object rather than the original top-level type, and we use vname to destructure a $<Both $vname> variant rather than the original variant name.
  • At // (2), we use the fprefix argument to give the second instance of our pattern a different set of binding names, so they will not conflict with the first instance.

Finally, we pass arguments to $vtype at // (3), so that instead of constructing an instance of the current variant, it constructs the *Diff type with an appropriate Both* name.

Other features

derive-deftly has many more features, that aren't yet explained in this tutorial. For example:

  • fvis, tvis, and approx_equal, more conditions for dealing with various cases by hand.

  • $tdeftype for defining a new data structure in terms of features of the input data structure, and $Xattrs for passing through attributes.

Full details are in the reference, which also has a brief example demonstrating each construct.