A procedural attribute macro to reduce boilerplate when writing functions that partially borrow a struct.
Overview
This crate attempts to make partial or "split" borrows[^1][^2][^3][^4][^5][^6] 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
adding #[clasma(&<...> Mystruct)] to a function definition fn foo(..) {}
- extends
foo's arguments with borrows of some ofMystruct's fields specified in&<...>and - generates a macro
foo!– essentially callable just likefoo's original signature but taking an additional an instancemystructofMystructto partially borrow
foo!;
expands to
foo;
where, again, the fields are specified between &<...>.
[^1]: Rust Internals "Notes on partial borrow". [^2]: The Rustonomicon "Splitting Borrows". [^3]: Niko Matsakis Blog Post "After NLL: Interprocedural conflicts". [^4]: Afternoon Rusting "Multiple Mutable References". [^5]: Partial borrows Rust RFC. [^6]: HackMD "My thoughts on (and need for) partial borrows".
Installation
[!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.
Motivating Example
Imagine a Tourist with a list of travel destinations, that counts the number of times he visits one of his destinations.
Before
Without partial borrowing, this code does not compile.
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:
However, one can imagine how tedious it would get, as the number of fields increases.
With clasma
The clasma macro handles borrowing and argument passing.
Usage
Consider the following struct:
let mut mystruct = Mystruct ;
The #[clasma(&<...> Mystruct)] attribute proc-macro expands foo's signature with the fields specified between &<...>. For example,
expands to
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!;
expands to
foo;
One can also optionally provide generic type parameters inside angle brackets <...>:
foo!;
expands to
;
#[clasma(&<...> Mystruct)] supports adding lifetime annotations to partially borrowed fields in &<...>, and one can likewise call foo! with lifetime parameters:
const mystruct: Mystruct = Mystruct ;
foo!;
expands to
const mystruct: Mystruct = Mystruct ;
;
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.
Generic structs
let mystruct = Mystruct
foo!;
expands to
let mystruct = Mystruct
;
Propagating from scope
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.
expands to
Partially borrowing several structs
Presuming that two structs have non-overlapping fields, one can partially borrow both structs with #[clasma(&<...> Struct1, &<...> Struct2)]:
let a = &A;
let c = &mut C;
let mut other = Other ;
foo!; // supplies the fields of `Mystruct` locally and fields of `Other` from `other`
expands to
let a = &A;
let c = &mut C;
let mut other = Other ;
;
impl blocks
For impl blocks, a #[clasma] attribute must go on top of the impl. One can then annotate inner functions with #[clasma(&<...> Mystruct)] like usual:
foo!;
expands to
foo;
If both the type and the function in the impl block are generic, one can provide the arguments separated by :: like so:
foo!;
// 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!