clasma 0.2.2

A procedural macro for partial borrowing
Documentation
clasma-0.2.2 has been yanked.

Crates.io Docs.rs

A procedural attribute macro to reduce boilerplate when passing partially borrow structs into functions.

Overview

The #[clasma(..)] function annotation generates a macro_rules! macro of the same name and does not touch the function signature. This macro is callable similarly to the original function, except that one can pass a struct instance to provide the arguments corresponding to its fields.

This attempts to make partial or "split" borrows more ergonomic in Rust, where the borrow checker sometimes requires functions to borrow references to individual fields rather than a single mutable reference to the parent struct, leading to verbose call sites.

Usage

Consider the following struct:

struct Mystruct {
    a: A,
    b: B,
    c: C,
}

let mut mystruct = Mystruct {
    a: A::new(),
    b: B::new(),
    c: C::new(),
};

By taking fields of Mystruct as arguments, the #[clasma(..)] attribute on a function foo will generate a macro foo!. Due to proc-macros being unable to resolve a struct identifier's field names, one must provide all field names of Mystruct to #[clasma(..)].

The first argument to the foo! is an instance of Mystruct. foo! borrows each of mystruct's fields individually that match foo's argument names. One passes the remaining arguments in the same order as the signature.

// Must list *all* field names of `Mystruct`
#[clasma(a,b,c)]
fn foo(a: &mut A, b: &B, some_arg: u8) {
    // ...
}

foo!(mystruct, 3);
// expands to:
// foo(&mut mystruct.a, &mystruct.b, 3);

One can reorder arguments; they just have to match with the struct's fields. One can also optionally provide generic parameters inside angle brackets <...>.

#[clasma(a,b,c)]
fn foo<T>(b: &B, some_arg: T, a: &mut A, c: &C) {
    // ...
}

foo!(<&str>, mystruct, "hello");
// expands to:
// foo::<&str>(&mystruct.b, "hello", &mut mystruct.a, &mystruct.c);

Lifetime parameters are also supported.

#[clasma(a,b,c)]
fn foo<'t: 'static>(b: &'t B, a: &'t A, other_arg: &'t u8) {
    // ...
}

const mystruct: Mystruct = Mystruct { ... };

foo!(<'static>, mystruct, &3);
// expands to:
// foo::<'static>(&mystruct.b, &mystruct.a, &3);

Propagating from scope

Two functions marked with identical #[clasma(..)] attributes can call one another using <function name>_scope! macros. One calls foo_scope! identically to foo!, except that one omits the mystruct. The arguments to foo previously supplied by mystruct now come directly from the local scope at the call site.

#[clasma(a,b,c)]
fn foo<T>(a: &A, other_arg: T, c: &mut C) {
    // ...
}

#[clasma(a,b,c)]
fn bar(c: &mut C, some_arg: u8, b: &B, a: &mut A) {
    
    foo_scope!(<u8>, some_arg); // supplies the fields of `Mystruct` from local scope
    // expands to:
    // foo::<u8>(a, some_arg, c)
}

impl-blocks

For impl blocks, the #[clasma(..)] attribute goes on top of the impl. This will generate macros for all functions in the block.

#[clasma(a,b,c)]
impl Mystruct {
    // ...
    fn foo(a: &mut A, b: &B, some_arg: u8) {
        // ...
    }

    fn bar(c: &mut C, some_arg: u8, b: &B, a: &mut A) {

        foo_scope!(some_arg);
        // expands to:
        // Mystruct::foo(a, b, some_arg)
    }
}

foo!(mystruct, 3);
// expands to:
// Mystruct::foo(&mut mystruct.a, &mystruct.b, 3);

If both the type and the function in the impl block are generic, one can provide the arguments separated by :: like so:

#[clasma(a,b,c)]
impl<T> Mystruct<T> {
    // ...
    fn foo<U>(a: &mut A, b: &B, some_arg: U, other_arg: T) {
        // ...
    }
}

foo!(<&str>::<u8>, mystruct, 3, "hello");
// expands to:
// Mystruct::<&str>::foo::<u8>(&mut mystruct.a, &mystruct.b, 3, "hello");

If only the type is generic, one passes the arguments like this: foo!(<T>::, mystruct, ...)

Motivating Example

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>
}

Before

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, this gets tedious as the number of fields increases.

With clasma::partial

The partial macro handles borrowing and argument passing.

#[clasma(n_visits, destinations)]
impl Tourist {
    // ...
    fn visit_all(&mut self) {
        for dest in &self.destinations {
            visit!(self, dest); // Now the call to `visit` becomes much more concise
        }
        println!("Visited {} countries", self.destinations.len());
    }
}

Installation

cargo add clasma