Handling generics
Every macro so far assumed the type was non-generic. The moment a user writes
struct Wrapper<T> { value: T }, a naive impl Wrapper { .. } is wrong — it’s missing the <T>,
and the body probably needs T to satisfy some bound. Handling generics correctly is the last
piece before you can write a macro people actually depend on.
What an impl header needs
For struct Wrapper<'a, T: Clone>, the impl you generate has to look like:
#![allow(unused)]
fn main() {
impl<'a, T: Clone> Wrapper<'a, T> { /* ... */ }
// ^^^^^^^^^^^^^^^ ^^^^^^^
// impl_generics ty_generics
}
Three distinct pieces, and they’re not identical: the impl<...> list carries bounds and
lifetimes, while the Wrapper<...> list is just the parameter names. Plus any where clause.
Getting these by hand is error-prone.
split_for_impl
syn’s Generics::split_for_impl
hands you all three, ready to interpolate:
#![allow(unused)]
fn main() {
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
quote! {
impl #impl_generics #name #ty_generics #where_clause {
// ...
}
}
}
Each piece knows how to render itself in the right spot: #impl_generics becomes <'a, T: Clone>,
#ty_generics becomes <'a, T>, and #where_clause becomes a where ... (or nothing). For a
non-generic type all three are empty, so the same code handles both cases.
Adding your own bounds
split_for_impl faithfully reproduces the user’s generics — but your generated code often needs
more. If your body calls format!("{:?}", ..) on a field of type T, then T: Debug must hold,
and the user didn’t write that. The fix is to add the bound before splitting: clone the generics,
push a bound onto each type parameter, then split the modified copy.
#![allow(unused)]
fn main() {
let mut generics = input.generics.clone();
for param in &mut generics.params {
if let syn::GenericParam::Type(type_param) = param {
type_param.bounds.push(syn::parse_quote!(::std::fmt::Debug));
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
}
Notes:
parse_quote!is likequote!, but it parses the tokens into asyntype — here aTypeParamBound. It’s the easiest way to build smallsynvalues from real syntax.- Match only on
GenericParam::Type; lifetimes (GenericParam::Lifetime) and const generics (GenericParam::Const) take no such bound. - Pushing onto a parameter’s
boundsadds to any bound the user already wrote (T: ClonebecomesT: Clone + Debug) rather than replacing it.
This blanket “add
Debugto every type parameter” is the same heuristic#[derive(Debug)]uses.
Exercise
The exercise for this section is located in 04_derive/12_generics
The worked example above added a Debug bound for DebugFields. For the exercise you’ll build a
different generic-aware macro, Empty, which generates an empty() -> Self constructor setting
every field to Default::default(). split_for_impl is already wired into the generated impl;
your job is the bound. Work out which trait bound the generated body now requires, and add it to
each type parameter the same way the example did. The plain test already passes — get generic
(which has type parameters) to pass too.