# darling: a proc-macro attribute parser built on the same ideas as serde

> darling is a Rust crate for the authors of procedural macros, and it gives you a way to parse an attribute into a struct with derive support, defaults, renaming, flattening and error messages that point at the right span in the user's source. The current version is 0.24.1 and it needs Rust 1.88 or newer, and the crate is deliberately split into a core library and a macro crate so the derive can be used without pulling the whole thing in.

**TedDriggs/darling** — A Rust proc-macro attribute parser

- Repository: https://github.com/TedDriggs/darling
- Stars: 1,276 · Forks: 98
- Language: Rust
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/teddriggs-darling

## What it does: attributes in, a struct out

darling is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by serde both in its internals and in its API, and the primary language is Rust with the licence MIT. The author is Ted Driggs.

The problem it solves is specific to procedural macro authors. When you write a derive macro that takes options, the user writes something like #[my_crate(lorem(dolor = "Hello", sit))], and your macro has to turn that token stream into a struct. Doing that by hand means walking the syn AST, matching on token shapes, collecting errors as you go and figuring out how to point a diagnostic at the right place. That is a lot of work, and it is the same work in every macro.

darling gives you a set of traits which can be derived or manually implemented, and there are five of them, and they form a hierarchy.

FromMeta is the leaf. It is used to extract values from a meta-item in an attribute, and implementations are likely reusable for many libraries, much like FromStr or serde::Deserialize. Trait implementations are provided for primitives, some std types, and some syn types. So FromMeta is the trait that knows how to turn "the value 15" into a u16, and you implement or use it for your own types.

FromDeriveInput is implemented or derived by each proc-macro crate which depends on darling. This is the root for input parsing, and it gets access to the identity, generics and visibility of the target type, and can specify which attribute names should be parsed or forwarded from the input AST. The word root matters, because it is the entry point for a derive macro.

FromField is derived by each proc-macro crate, and structs deriving it get access to the identity, if it exists, the type and the visibility of the field. FromVariant is the variant equivalent, giving access to the identity and contents of the variant, which can be transformed the same as any other darling input.

FromAttributes is the lower level version of the three specific traits. Structs deriving it get a meta-item extractor and error collection which works for any syntax element, including traits, trait items and functions, and the documentation says this is useful for non-derive proc macros.

That last point is the design reason the trait set has the shape it does. The four specific traits exist so a derive macro gets a typed view of the thing it is deriving on, and the general one exists for the cases where there is no such thing, which is every attribute macro that is not a derive.

## The example, and how forwarding works

The README's example is the clearest description of the whole flow, and it is short.

```rust,ignore
use darling::{FromDeriveInput, FromMeta};

#[derive(Default, FromMeta)]
#[darling(default)]
pub struct Lorem {
    #[darling(rename = "sit")]
    ipsum: bool,
    dolor: Option<String>,
}

#[derive(FromDeriveInput)]
#[darling(attributes(my_crate), forward_attrs(allow, doc, cfg))]
pub struct MyTraitOpts {
    ident: syn::Ident,
    attrs: Vec<syn::Attribute>,
    lorem: Lorem,
}
```

And the input it parses is:

```rust,ignore
/// A doc comment which will be available in `MyTraitOpts::attrs`.
#[derive(MyTrait)]
#[my_crate(lorem(dolor = "Hello", sit))]
pub struct ConsumingType;
```

Several mechanisms are visible at once.

The attributes(my_crate) declaration says which attribute names this derive should parse. The forward_attrs(allow, doc, cfg) says which other attributes on the item should be collected and handed over untouched, which is how a derive macro that does not own an attribute still gets access to the standard ones. The doc comment is there deliberately, and the comment says it will be available in MyTraitOpts::attrs, so you can see the forwarding working.

The rename on the field says the user writes sit and the struct field is ipsum. The default on the struct says the whole struct can be omitted. And the Option<String> for dolor is optional without any annotation, which the features section explains: Option<T> and darling::util::Flag fields are innately optional, you don't need to declare #[darling(default)] for those.

The ident field is one of the auto-populated fields. Structs deriving FromDeriveInput and FromField can declare properties named ident, vis, ty, attrs and generics to automatically get copies of the matching values from the input AST. FromDeriveInput additionally exposes data to get access to the body of the deriving type, and FromVariant exposes fields.

So the two mechanisms that do the work are the ones you write, which are the FromMeta structs like Lorem, and the ones darling fills in for you, which are the well-known field names.

The documentation also points at a larger example, examples/serde.rs, that re-implements serde's attribute parsing. That is a genuinely useful reference if you are building something similar, and the examples directory has a dozen more, including automatic_bounds.rs, consume_fields.rs, derive.rs, enum_of_struct_variants.rs, expr_with.rs, fallible_read.rs, from_word_and_expr.rs, heterogeneous_enum_and_word.rs, html.rs, shorthand_or_long_field.rs and supports_struct.rs.

## Attribute macros, and the NestMeta step that makes them work

Non-derive attribute macros are supported, and the mechanism is worth understanding because it is the part that differs most from a derive.

The instruction is: to parse arguments for attribute macros, derive FromMeta on the argument receiver type, then use darling::ast::NestedMeta::parse_meta_list to convert the arguments TokenStream to a Vec<NestedMeta>, then pass that to the derived from_list method on your argument receiver type. This will produce a normal darling::Result<T> that can be used the same as a result from parsing a DeriveInput.

So the pipeline is the same as for a derive, with one extra step. The attributes of an attribute macro arrive as an argument token stream, and NestedMeta::parse_meta_list is what turns that into the structured form darling works with, and from_list is the entry point the derive generates for a FromMeta type.

The macro code example shows the whole thing including the error handling:

```rust,ignore
use darling::{Error, FromMeta};
use darling::ast::NestedMeta;
use syn::ItemFn;
use proc_macro::TokenStream;

#[derive(Debug, FromMeta)]
#[darling(derive_syn_parse)]
struct MacroArgs {
    #[darling(default)]
    timeout_ms: Option<u16>,
    path: String,
}

#[proc_macro_attribute]
pub fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream {
    let _args: MacroArgs = match syn::parse(args) {
        Ok(v) => v,
        Err(e) => { return e.to_compile_error().into(); }
    };
    let _input = syn::parse_macro_input!(input as ItemFn);

    // do things with `args`
    unimplemented!()
}
```

And the consuming side is what a user writes:

```rust,ignore
use your_crate::your_attr;

#[your_attr(path = "hello", timeout_ms = 15)]
fn do_stuff() {
    println!("Hello");
}
```

The derive_syn_parse attribute is the piece specific to this case. It is what makes the derived from_list method able to accept the token stream directly, rather than requiring the manual NestedMeta::parse_meta_list step. The example is marked as ignoring the exact syntax, and it shows syn::parse(args) being used directly, which is the shorthand the derive_syn_parse attribute enables.

One detail in the error handling is good practice and worth copying. The match on syn::parse turns the error into a compile error token with e.to_compile_error().into(), so a user who mistypes an option gets a compiler error at the attribute rather than a panic in the macro. A proc macro that panics produces an unhelpful message and a confusing backtrace, and darling is built so that path is not the normal one.

## Where darling sits against hand written parsing

It is worth being clear about what you give up by adopting darling, because the crate is not a drop-in for every macro.

If your macro takes no attributes at all, darling is the wrong tool. The whole crate is about attribute input, and the trait set starts at FromMeta, which extracts a value from a meta-item in an attribute. A macro that only transforms a token stream does not need any of it.

If your macro takes exactly one option and nothing else, then darling may be more machinery than the problem needs. Parsing a single boolean keyword out of a token stream is not much work, and the derive plus the struct plus the generated code may exceed the effort it saves. The break-even point is roughly when you have options with types that need real parsing, defaults, or error messages that point at the right place.

The other cost is the compile time. darling is a proc-macro crate, so every consumer compiles the macro half, and the macro half brings syn with it. A project that already depends on syn for its own macros pays nothing extra, but a project with no other proc macros now has one.

And then there is the version question. The crate is at 0.24.1, and 0.x versions in Rust do not carry the stability promise that 1.0 does. The release history shows v0.24.0 and v0.24.1 within a month of each other in mid 2026, and v0.23.0 before that, so the API is still moving. That is fine for a macro crate where the consumer controls the version through their own dependency, and it is the reason the README keeps a changelog and the repository has a CHANGELOG.md.

The upside is the part that makes the decision easy for most macro authors. The two things every proc macro author wants and usually rebuilds are good error messages and a declarative option model. darling gives both, and the error messages are the part you cannot easily write yourself: spans from SpannedValue, precise locations from write_errors, and did you mean suggestions for misspelled fields, all in a crate you would otherwise have to assemble from syn APIs.

So the case for it is strongest exactly where the work is most tedious, which is a macro with a dozen options, some of them with defaults, some of them optional, some of them renamed, and users who will make mistakes in them.

## The features that matter, and the two crates underneath

The features list is where the practical value of the crate is, and a few entries are worth pulling out.

Defaults use the same path syntax as serde, and they work at struct and field level. Field renaming lets a field have a different name in usage than in the backing code, which is what the rename in the example does.

The mapping function is the most powerful one. Use #[darling(map="path")] or #[darling(and_then="path")] to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct. The difference between map and and_then is the composition semantics, and the point of both is that the parsed representation does not have to be the representation you store. That is the same idea as serde's from and into, and it is what makes the crate usable for types that have no natural attribute syntax.

Struct flattening removes one level of structure when presenting your meta item to users, so a nested group of options reads as a flat set.

Multiple-occurrence fields use #[darling(multiple)] on a Vec field to allow that field to appear multiple times in the meta-item, with each occurrence pushed into the Vec. That is a small feature that you would otherwise hand-roll, and it is the kind of thing that makes an attribute syntax pleasant to use.

Span access is the feature that makes the error messages good. Use darling::util::SpannedValue in a struct to get access to that meta item's source code span, which can be used to emit warnings that point at a specific field from your proc macro. And darling::Error::write_errors gives precise error location details in most cases automatically. The did you mean suggestions come from the suggestions feature, which is on by default, and compile errors from derived darling trait implementations include suggestions for misspelled fields.

The transformation of forwarded attributes is also worth noting. You can add #[darling(with=path)] to the attrs field to use a custom function to transform the forwarded attributes before they are provided to your struct, the function signature being fn(Vec<Attribute>) -> darling::Result<T>, and returning an error from it propagates with all the other parsing errors.

And the crate structure underneath. The Cargo.toml has darling_core and darling_macro as path dependencies pinned to the exact same version, =0.24.1, and the workspace members are macro and core. The split is the standard one for a proc-macro crate, since the macro implementation has to be a proc-macro crate while the core library cannot be, and a user who only wants the runtime types does not need to compile the macro half.

The feature flags line up with that. default is suggestions, and then there is diagnostics, suggestions and serde as optional features that forward to darling_core. The serde feature is the compatibility path for the many macros that want to accept serde-style options, and the fact that it is optional rather than always on keeps the core small.

The remaining metadata is worth noting for anyone pinning versions. The edition is 2021, rust-version is 1.88.0, and the badge in the README says rustc 1.88.0 or newer, so the crate sets a real floor. The dev dependencies are proc-macro2, quote and syn 3.0.0, and there is a target-specific dev dependency block for cfg(compiletests) that pulls in rustversion and trybuild, which is the standard way to test that a macro produces the expected compiler error, with the lints.rust table declaring that cfg as expected so the check-cfg lint does not complain.

The recent releases are v0.24.1 on 2026-08-20, v0.24.0 on 2026-07-30 and v0.23.0 on 2025-12-03, and the repository is not archived with the last push on 2026-09-11.

## Conclusion

darling fits a Rust developer writing a derive or attribute macro who wants the parsing and the error reporting to be a solved problem rather than a month of work, and who is willing to model their options after serde's conventions so the API is familiar. It is the wrong tool if your macro does not take attributes, since the whole crate is about attribute input, and the wrong choice if you need a stable 1.0 API, because at 0.24.x the semver contract is still moving. Before adopting, read the features list against what your macro needs, particularly the flatten and multiple-occurrence behaviours, and check that the Rust version your project pins is at or above the 1.88 the crate requires.

## FAQ

### What is the darling crate for?

It is for proc macro authors, and it enables parsing attributes into structs. It provides a set of traits, FromMeta, FromDeriveInput, FromField, FromVariant and FromAttributes, which can be derived or manually implemented, so a derive macro can take an attribute like #[my_crate(lorem(dolor = "Hello", sit))] and turn it into a typed struct with defaults, renaming and validation.

### How do I use darling in a non-derive attribute macro?

Derive FromMeta on your argument receiver type, then use darling::ast::NestedMeta::parse_meta_list to convert the arguments TokenStream to a Vec<NestedMeta>, and pass that to the derived from_list method. The result is a darling::Result<T> used the same way as a result from parsing a DeriveInput. Adding #[darling(derive_syn_parse)] lets you use syn::parse directly instead of the manual step.

### What error reporting does darling provide?

Compile errors from derived darling implementations include suggestions for misspelled fields through the did you mean feature, which is on by default. You can also use darling::util::SpannedValue in a struct to get the source code span of a meta item and emit warnings pointing at a specific field, and darling::Error::write_errors gives precise error location details in most cases.

### What Rust version and dependencies does darling need?

The crate requires Rust 1.88.0 or newer and uses edition 2021. It is split into darling_core and darling_macro, pinned to the exact same version, and the workspace members are macro and core. The features are default, which is suggestions, plus diagnostics, suggestions and serde, all of which forward to darling_core.

## Sources

- [Issues](https://github.com/TedDriggs/darling/issues)
- [License: MIT](https://github.com/TedDriggs/darling/blob/master/LICENSE)
- [README](https://github.com/TedDriggs/darling/blob/master/README.md)
- [Releases](https://github.com/TedDriggs/darling/releases)
- [TedDriggs/darling on GitHub](https://github.com/TedDriggs/darling)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/teddriggs-darling
