| Crates.io | clasma |
| lib.rs | clasma |
| version | 1.0.2 |
| created_at | 2025-08-07 12:48:29.896849+00 |
| updated_at | 2025-08-21 18:10:05.405718+00 |
| description | A procedural macro for partial borrowing |
| homepage | |
| repository | https://github.com/spherinder/clasma |
| max_upload_size | |
| id | 1785230 |
| size | 481,364 |
A procedural attribute macro to reduce boilerplate when writing functions that partially borrow a struct.
This crate attempts to make partial or "split" borrows123456 more ergonomic in Rust – a problem where functions sometimes must borrow individual fields, rather than a single mutable reference to a struct, in order to adhere to borrowing rules, leading to verbose call sites.
With a struct definition like
#[clasma]
struct Mystruct { ... }
adding #[clasma(&<...> Mystruct)] to a function definition fn foo(..) {}
foo's arguments with borrows of some of Mystruct's fields specified in &<...> andfoo! – essentially callable just like foo's original signature but taking an additional an instance mystruct of Mystruct to partially borrow#[clasma(&<...> Mystruct)]
fn foo( ... ) { ... }
foo!( ... , mystruct);
expands to
fn foo( ..., <field>: &<field_type>, <field>: &<field_type>... ) { ... }
foo( ... , &mystruct.<field>, &mystruct.<field>);
where, again, the fields are specified between &<...>.
[!IMPORTANT]
clasmarequires#[feature(decl_macro)]This is in order to ensure, that
path::to::foo!expands topath::to::fooas opposed to justfoo.macro_rules!macros are unhygienic in this regard.
cargo add clasma
Imagine a Tourist with a list of travel destinations, that counts the number of times he visits one of his destinations.
struct Tourist {
n_visits: u32,
destinations: Vec<String>
}
Without partial borrowing, this code does not compile.
impl Tourist {
fn visit(&mut self, dest: &String) {
if self.destinations.contains(dest) {
self.n_visits += 1;
}
}
fn visit_all(&mut self) {
for dest in &self.destinations { // <- immutable borrow occurs here
self.visit(dest); // ERROR: `*self` is also borrowed as immutable
}
println!("Visited {} countries", self.destinations.len());
}
}
The issue is that visit unnecessarily borrows self.destinations mutably when it only needs an immutable borrow. A workaround would be to borrow each struct field separately on every call to visit:
impl Tourist {
fn visit(destinations: &Vec<String>, n_visits: &mut u32, dest: &String) {
if destinations.contains(dest) {
*n_visits += 1;
}
}
fn visit_all(&mut self) {
for dest in &self.destinations {
Self::visit(&self.destinations, &mut self.n_visits, dest);
}
println!("Visited {} countries", self.destinations.len());
}
}
However, one can imagine how tedious it would get, as the number of fields increases.
clasmaThe clasma macro handles borrowing and argument passing.
#[clasma]
struct Tourist { ... }
#[clasma]
impl Tourist {
#[clasma(&<destinations, mut n_visits> Tourist)]
fn visit(dest: &String) {
if destinations.contains(dest) {
*n_visits += 1;
}
}
// ...
fn visit_all(&mut self) {
for dest in &self.destinations {
visit!(self, dest); // Now calls to `visit` become more concise
}
println!("Visited {} countries", self.destinations.len());
}
}
Consider the following struct:
#[clasma]
struct Mystruct {
a: A,
b: B,
c: C,
}
let mut mystruct = Mystruct {
a: A::new(),
b: B::new(),
c: C::new(),
};
The #[clasma(&<...> Mystruct)] attribute proc-macro expands foo's signature with the fields specified between &<...>. For example,
#[clasma(&<mut a, b> Mystruct)]
fn foo(some_arg: u8) {
// ...
}
expands to
fn foo(some_arg: u8, a: &mut A, b: &B) {
// ...
}
Additionally, #[clasma(&<...> Mystruct)] generates a macro foo!. The first arguments to foo! are the arguments of foo's original signature (some_arg: u8) and the final argument is an instance of Mystruct. foo!(..., mystruct) borrows each of mystruct's fields individually, passing them into foo's corresponding arguments.
foo!(3, mystruct);
expands to
foo(3, &mut mystruct.a, &mystruct.b);
One can also optionally provide generic type parameters inside angle brackets <...>:
#[clasma(&<a, b> Mystruct)]
fn foo<T>(some_arg: T) {
// ...
}
foo!(<&str>, "hello", mystruct);
expands to
fn foo<T>(some_arg: T, a: &A, b: &B) {
// ...
}
foo::<&str>("hello", &mystruct.a, &mystruct.b);
#[clasma(&<...> Mystruct)] supports adding lifetime annotations to partially borrowed fields in &<...>, and one can likewise call foo! with lifetime parameters:
const mystruct: Mystruct = Mystruct { ... };
#[clasma(&<'t a, 't b> Mystruct)]
fn foo<'t: 'static, T>(other_arg: &'t u8) {
// ...
}
foo!(<'static>, &3, mystruct);
expands to
const mystruct: Mystruct = Mystruct { ... };
fn foo<'t: 'static, T>(other_arg: &'t u8, a: &'t A, b: &'t B) {
// ...
}
foo::<'static>(&3, &mystruct.a, &mystruct.b);
In addition to enumerating field names in &<...> manually, one can also use a wildcard * to specify all fields, and ! to exclude a field.
For example, &<'t mut a, 's *, mut b, !c> Mystruct is equivalent to &<'s a, 's b> Mystruct.
#[clasma]
struct Mystruct<T,U> {
a: Vec<T>,
b: Vec<(T,U)>,
c: U,
}
#[clasma(&<*> Mystruct<T, bool>)]
fn foo<U>(some_arg: U) {
// ...
}
let mystruct = Mystruct {
a: vec!["hi"],
b: vec![("bye", false)],
c: true,
}
foo!(<&str>, "hello", mystruct);
expands to
struct Mystruct<T,U> { ... }
fn foo<U>(some_arg: T, a: &Vec<U>, b: &Vec<(U,bool)>, c: &bool) {
// ...
}
let mystruct = Mystruct {
a: vec!["hi"],
b: vec![("bye", false)],
c: true,
}
foo::<&str>("hello", &mystruct.a, &mystruct.b, &mystruct.c);
In addition to partially borrowing fields from a struct instance, foo! also accepts .. in place of the struct instance. The arguments to foo previously supplied by mystruct, are now read directly from the local scope at the call site.
#[clasma(&<a, mut c> Mystruct)]
fn foo<T>(some_arg: T) {
// ...
}
#[clasma(&<*> Mystruct)]
fn bar() {
let c = &mut C::new();
foo!(<u8>, 3, ..); // supplies the fields of `Mystruct` from local scope
}
expands to
fn foo<T>(some_arg: T, a: &A, c: &mut C) {
// ...
}
fn bar(a: &A, b: &B, c: &C) {
let c = &mut C::new();
foo::<u8>(3, a, c);
}
Presuming that two structs have non-overlapping fields, one can partially borrow both structs with #[clasma(&<...> Struct1, &<...> Struct2)]:
#[clasma]
struct Other {
o1: A,
o2: B,
o3: C,
}
#[clasma(&<a, mut c> Mystruct, &<mut o2, o3> Other)]
fn foo<T>(some_arg: T) {
// ...
}
let a = &A();
let c = &mut C();
let mut other = Other {
o1: A::new(),
o2: B::new(),
o3: C::new(),
};
foo!(<u8>, 3, .., other); // supplies the fields of `Mystruct` locally and fields of `Other` from `other`
expands to
struct Other { ... }
fn foo<T>(some_arg: T, a: &A, c: &mut C, o2: &mut B, o3: &C) {
// ...
}
let a = &A();
let c = &mut C();
let mut other = Other {
o1: A::new(),
o2: B::new(),
o3: C::new(),
};
foo::<u8>(3, a, c, &mut other.o2, &other.o3);
impl blocksFor impl blocks, a #[clasma] attribute must go on top of the impl. One can then annotate inner functions with #[clasma(&<...> Mystruct)] like usual:
#[clasma]
impl Mystruct {
// ...
#[clasma(&<mut a, b) Mystruct]
fn foo(some_arg: u8) {
// ...
}
#[clasma(&<mut *, b)]
fn bar(other_arg: u8) {
foo!(other_arg, ..);
}
}
foo!(3, mystruct);
expands to
impl Mystruct {
// ...
fn foo(some_arg: u8, a: &mut A, b: &B) {
// ...
}
#[clasma(&<mut *, b)]
fn bar(other_arg: u8, a: &mut A, b: &B, c: &mut C) {
Mystruct::foo(other_arg, a, b)
}
}
Mystruct::foo(3, &mut mystruct.a, &mystruct.b);
If both the type and the function in the impl block are generic, one can provide the arguments separated by :: like so:
#[clasma]
impl<T> Mystruct<T> {
// ...
#[clasma(&<mut a, b> Mystruct<T>)]
fn foo<U>(a: &mut A, b: &B, some_arg: U, other_arg: T) {
// ...
}
}
foo!(<&str>::<u8>, 3, "hello", mystruct);
// expands to:
// Mystruct::<&str>::foo::<u8>(3, "hello", &mut mystruct.a, &mystruct.b);
If only the type is generic, one can omit the second pair of angle brackets and pass the arguments like this:
foo!(<T>::, ...)