synbounds is a Rust crate that provides utilities for writing proc-macro crates that need to manipulate or analyze Rust type bounds.
It leverages the syn crate to parse and work with Rust syntax trees, making it easier to handle complex type bounds in procedural macros.
- Track Generic Usage: Automatically determine which generic parameters (types, lifetimes, consts) are actually used in your code
- Extract Minimal Bounds: Generate minimal
whereclauses containing only the predicates for used generics - Lifetime Substitution: Utilities for replacing lifetimes in syntax trees
- Self Type Substitution: Replace
Selfwith concrete types in method signatures and return types
Add this to your Cargo.toml:
[dependencies]
synbounds = "0.1"use syn::{parse_quote, DeriveInput};
use syn::visit::Visit;
use synbounds::BoundGenerics;
// Parse a struct definition
let input: DeriveInput = parse_quote! {
struct MyStruct<'a, T, U> {
field: &'a T,
}
};
// Track which generics are used in the fields
let mut bounds = BoundGenerics::new(&input.generics);
if let syn::Data::Struct(data) = &input.data {
bounds.visit_fields(&data.fields);
}
// Only T and 'a are used, U is not
let bound_generics = bounds.to_bound_generics();
// bound_generics is now: <'a, T>use syn::{parse_quote, DeriveInput};
use syn::visit::Visit;
use synbounds::BoundGenerics;
let input: DeriveInput = parse_quote! {
struct MyStruct<T, U>
where
T: Clone,
U: Default,
{
field: T,
}
};
let mut bounds = BoundGenerics::new(&input.generics);
if let syn::Data::Struct(data) = &input.data {
bounds.visit_fields(&data.fields);
}
// Only get where predicates for used generics (T)
let where_predicates: Vec<_> = bounds.bound_where_predicates().collect();
// where_predicates contains only: T: Cloneuse syn::{parse_quote, ItemImpl};
use syn::visit::Visit;
use synbounds::BoundGenerics;
let item: ItemImpl = parse_quote! {
impl<T, U, V> MyStruct<T> {
fn process(&self, value: U) -> String {
unimplemented!()
}
}
};
// Track which generics are used
let mut bounds = BoundGenerics::new(&item.generics);
bounds.visit_type(&item.self_ty);
for impl_item in &item.items {
if let syn::ImplItem::Fn(method) = impl_item {
bounds.visit_signature(&method.sig);
}
}
// T and U are used, V is not
let bound_generics = bounds.to_bound_generics();use syn::{parse_quote, Type};
use syn::visit_mut::VisitMut;
use synbounds::substitute_with_static_lifetime;
let mut ty: Type = parse_quote! { &'a String };
let mut visitor = substitute_with_static_lifetime::<()>();
visitor.visit_type_mut(&mut ty);
// ty is now: &'static Stringuse syn::{parse_quote, Type};
use syn::visit_mut::VisitMut;
use synbounds::SubstituteSelfType;
let concrete_type: Type = parse_quote! { MyStruct<T, U> };
let mut return_type: Type = parse_quote! { Result<Self, Error> };
let mut visitor = SubstituteSelfType::new(&concrete_type);
visitor.visit_type_mut(&mut return_type);
// return_type is now: Result<MyStruct<T, U>, Error>This is especially useful when defining private wrapper objects for functions that use Self.
proc-macro(default): Enable proc-macro supportfull: Enable fullsynfeature for additional syntax supportsubstitute(default): Enable lifetime substitution utilities
This crate is particularly useful when:
- Writing derive macros that need to determine which generic parameters are actually used
- Generating trait implementations with minimal bounds
- Analyzing AST nodes to extract relevant generic constraints
- Creating procedural macros that manipulate lifetime parameters
When writing a proc-macro for a specific library, I came across the problem, that I needed to know
which generic bounds where actually used on a specific part of the AST (Functions in impl blocks
to be precise).
While syn provides the necessary structures to parse and represent these bounds, it does not
provide utilities to easily extract or manipulate them. So I first wrote some helper functions for
my specific use case.
Later I came across the synstructure crate, which provided some similar functionality, but was
more focused on deriving implementations for enums and structs. But this inspired me to generalize
and refactor my helper functions into a standalone crate that could be reused in other
proc-macro projects.
This project is licensed under either of
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
at your option
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.