dtolnay/proc-macro-workshop: five Rust macro projects with failing tests as the spec
Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]
At a glance
- What is it?
- A hands-on workshop repository that teaches derive, attribute and function-like procedural macros through five real-world projects. The tests define the target; the README is deliberately thin on solutions, which is both the point and the friction.
- Who is it for?
- Anyone who already writes Rust generics and trait bounds and wants to learn proc macros by building them should start with the builder project and work forward. People who have never written a trait impl, or who want a copy-paste macro crate rather than an exercise, should look elsewhere.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 56 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What proc-macro-workshop is, and who it is actually for
This is a workshop, not a library. The README describes it as "a selection of projects designed to learn to write Rust procedural macros", and it says three of the five projects are macros the author implemented in industrial codebases, while the other two exist as crates.io libraries by other authors. That framing matters: the exercises are drawn from real use cases rather than toy examples, so the problems you hit are the problems macro authors hit.
The intended audience is narrower than "Rust developers". The README states plainly that the content assumes a working understanding of structs, enums, traits, trait impls, generic parameters and trait bounds, and adds that these basics are far easier to learn outside the context of macros. If you cannot write a generic function with a where clause without looking it up, the exercises will be frustrating rather than instructive. If you can, the workshop puts you directly in front of syntax trees, token streams and trait-bound inference, which is where proc macro work actually lives.
How the five projects are structured and what each one teaches
The repository is a Cargo workspace. The root Cargo.toml declares a [workspace] with a single [[bin]] named workshop pointing at main.rs, and five path dependencies: bitfield, derive_builder, derive_debug, seq and sorted. Each of those directories holds one project skeleton. The package is marked publish = false, so nothing here is meant to ship to crates.io.
The projects split across the three macro kinds. derive(Builder) generates builder-pattern boilerplate, and the README recommends following std::process::Command's convention where setters take and return &mut self for chaining. derive(CustomDebug) implements Debug with per-field format strings, and is the project that deals with lifetime parameters, type parameters and inferring trait bounds on generic parameters of trait impls, plus what the README calls "limitations of derive's ability to emit universally correct trait bounds". seq! is a function-like macro for compile-time loops, demonstrated by generating an enum with variants Cpu0 through Cpu511. #[sorted] is an attribute macro that reports unsorted enum variants as a compile-time error, and it covers the visitor pattern over a syntax tree plus "limitations of the currently stable macro API". #[bitfield] defines structs in a packed binary representation with access to bit ranges, in the spirit of C bit fields.
The common thread is that each project covers traversing syntax trees, constructing output source code, and handling helper attributes. That is the actual mechanism of procedural macros: you receive a TokenStream, parse it into a syntax tree, and emit new Rust source as another TokenStream. The workshop's value is that it makes you do that five times with different constraints rather than once.
Installing proc-macro-workshop and running the first failing test
There is no published crate and no installer. You get the code by cloning the repository and building it with Cargo. The README does not give an explicit clone command, so the step below is the standard one for a repository of this shape; the build and test steps follow from the workspace layout in Cargo.toml.
git clone https://github.com/dtolnay/proc-macro-workshop
cd proc-macro-workshop
cargo testThe workspace has no external dependencies beyond the five local path crates, so cargo test compiles the skeletons and then fails. That failure is the starting point: the tests under each project directory encode the behaviour your macro has to produce. The README's Workflow section describes the recommended loop, and the Test harness section explains how testing is set up. The intended reading is that you open a project, read its tests, and edit the macro until the tests pass.
The README gives a concrete example of what the finished derive(Builder) macro should accept:
use derive_builder::Builder;
#[derive(Builder)]
pub struct Command {
executable: String,
#[builder(each = "arg")]
args: Vec<String>,
current_dir: Option<String>,
}
fn main() {
let command = Command::builder()
.executable("cargo".to_owned())
.arg("build".to_owned())
.arg("--release".to_owned())
.build()
.unwrap();
assert_eq!(command.executable, "cargo");
}Notice the #[builder(each = "arg")] helper attribute. That single line is where most of the work hides: your macro has to parse the attribute, recognize each, and generate a setter that appends to the Vec instead of replacing it. The current_dir field being Option<String> is the second trap, since a correct builder typically does not require optional fields to be set.
Where the workshop gets hard: trait bounds, error reporting and stable-API limits
The README does not hide the difficulty, but it also does not hand you answers. Two sections name problems that are genuinely hard rather than merely tedious.
First, CustomDebug. Deriving a trait for a generic type means deciding what bounds the generated impl needs. The naive answer, adding T: Debug for every type parameter, is wrong for types that wrap their parameter in something like Arc or PhantomData. The README lists "inferring trait bounds on generic parameters of trait impls" and "limitations of derive's ability to emit universally correct trait bounds" as topics. Those two lines describe a real, long-standing constraint of derive macros: the macro sees the syntax, not the resolved types, so it cannot always know which bounds are necessary. Working through this project is the fastest way to understand why so many derive crates ask you to spell out bounds manually.
Second, #[sorted]. The macro's job is not to generate code but to produce a good compile error. The README says it covers "compile-time error reporting" and "limitations of the currently stable macro API and some ways to work around them". Emitting an error that points at the right variant, with a span that lands on the offending line rather than on the whole enum, is a different skill from emitting working code, and the stable API does not make all of it easy. If you have only written derive macros that silently produce code, this project is the one that changes how you think about diagnostics.
What the workshop does not give you
The README is an index, not a textbook. It introduces each project in a few paragraphs and then points at the skeleton directory. There is no walkthrough of a finished solution in the repository, and no release has been published, so you cannot check your work against an official answer key inside the repository. The tests are the specification, which means the quality of your learning depends on how well you read them.
That is a deliberate design choice and it has a cost. When a test fails with a type error inside generated code, the error message points at code you did not write, in a crate you did not author. Debugging that requires understanding how proc macro output is expanded, and the README's Debugging tips section is the only help offered. If you learn best from worked examples rather than from failing tests, this format will slow you down.
There is also a scope limit. The workshop covers attribute, derive and function-like macros. It does not cover declarative macro_rules!, and it does not cover the practical packaging questions that come with shipping a macro crate, such as versioning a proc-macro crate or documenting generated APIs. The root package is publish = false, which is consistent with that: this is a learning workspace, not a template for a production crate.
proc-macro-workshop compared with reading a real derive crate instead
The obvious alternative is to skip the workshop and read the source of a production derive crate, such as the published derive_builder or the standard library's own derives. The difference in approach is significant.
Reading a finished crate shows you one working solution, already shaped by years of edge cases, and it is hard to tell which parts are essential and which are historical accident. The workshop inverts this: you get a skeleton and a set of tests, and you have to discover which parts of the problem are essential by hitting them. The failing tests tell you when you are wrong in a way that reading code never does.
The trade-off runs the other way too. A finished crate is a reference you can return to; the workshop is a sequence you complete once. If your goal is to fix a specific bug in an existing macro, reading that macro's source is faster and more targeted. If your goal is to be able to write a new macro from scratch, the workshop's five different problem shapes will teach you more than reading one crate's implementation. A reasonable path is to do the workshop's builder project first and then read a published builder crate to see how a mature implementation differs from your exercise.
Maintenance status, licence and the cost of keeping up
The repository is not archived, and the last push was on 2026-08-06. That is recent enough that the code has been touched within the last few months, though the workshop's content is tied to the stable macro API rather than to a fast-moving surface.
Upgrade cost is low by construction. The workspace has no external dependencies, only the five local path crates, and the package is publish = false, so there is nothing to version and nothing for downstream users to break. The main thing that could age is the stable macro API itself; the #[sorted] project explicitly deals with its limitations, so if that API changes, that project is where the exercises would need updating.
On licensing: the repository contains both LICENSE-APACHE and LICENSE-MIT, and the primary language metadata lists Apache-2.0. The README does not state how the dual licensing is meant to apply, and this article is not legal advice. If you intend to reuse any skeleton code in your own project, read both licence files in the repository root and confirm which terms you are operating under before you copy anything.
Editorial conclusion
Anyone who already writes Rust generics and trait bounds and wants to learn proc macros by building them should start with the builder project and work forward. People who have never written a trait impl, or who want a copy-paste macro crate rather than an exercise, should look elsewhere. Before starting, run cargo test in the workspace root and read the test file under builder/tests to confirm the failing-test workflow matches how you like to learn; also check the LICENSE-APACHE and LICENSE-MIT files since the repository ships both.
Frequently asked questions
What are procedural macros in Rust, and how does proc-macro-workshop teach them?
Procedural macros are Rust code that generates Rust code. The workshop covers all three kinds (derive, attribute and function-like) across five projects, each with a skeleton directory and tests that define the expected behaviour.
How do I code my own macro with proc-macro-workshop?
Clone the repository, run cargo test in the workspace root, then pick a project directory such as builder and edit its macro until the tests pass. The README's Workflow and Test harness sections describe the recommended loop.
What is a macro in programming?
In this repository a macro means a Rust procedural macro: code that runs at compile time, receives a token stream, and emits new Rust source. The README describes the five projects as drawn from real use cases rather than abstract examples.
How do I get proc-macro-workshop?
There is no published crate and no installer. You clone the GitHub repository and build it with Cargo; the root Cargo.toml declares a workspace with five local path dependencies and a single workshop binary.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dtolnay-proc-macro-workshop)