Macro compost::decompose

source ·
macro_rules! decompose {
    (...$input:expr) => { ... };
    (...$input:expr => (
        $($ty:ty),*$(,)?
    )) => { ... };
    ($input:expr) => { ... };
    ($input:expr => (&mut ...)) => { ... };
    ($input:expr => (
        $($ty:ty),*$(,)?
    )) => { ... };
    ($input:expr => $rest:ident & {
        $($name:ident: $ty:ty),*
        $(,)?
    }) => { ... };
    ($input:expr => {
        $($name:ident: $ty:ty),*
        $(,)?
    }) => { ... };
}
Expand description

This macro takes a mutable reference to a tuple and decomposes it into a sub-tuple (i.e. a tuple containing a subset of the values contained in the parent tuple).

Syntax

There are three ways in which this macro can be used…

…in an expression:

Jump to the “summary” section

These forms are useful when calling a function with a desired subset of the available context.

use compost::decompose;

let mut input = (1i32, &mut 2u32, 'c');

fn example(cx: (&i32, &mut u32)) {
    assert_eq!(cx, (&1, &mut 2));
}

// Can be used when calling a function...
example(decompose!(input));

// ...or when assigning to a variable.
let cx_subset: (&mut u32, &mut char) = decompose!(input);
assert_eq!(cx_subset, (&mut 2, &mut 'c'));

// Which is equivalent to:
let cx_subset = decompose!(input => (&mut u32, &mut char));
assert_eq!(cx_subset, (&mut 2, &mut 'c'));
…in a statement:

Jump to the “summary” section

These forms are useful for pulling context contained in a tuple into scope.

use compost::decompose;

let mut input = (1i32, &mut 2u32, 'c', 5u8);

// Brings component references into scope and produces a `rest` value containing
//the remaining components.
//
// NOTE: Because `rest`'s tuple layout is unspecified, `rest` is new-typed in a
// macro-internal `TupleRemainder` struct to allow for backwards-compatibility-
// preserving changes to the maximum arity, the search mechanism, etc.
decompose!(input => rest & {
    my_char: &mut char,
    my_i32: &i32,
});

assert_eq!((my_char, my_i32), (&mut 'c', &1));

// `rest` can itself be decomposed several times.
decompose!(rest => rest & { my_u32: &u32 });

// If you're done decomposing, you can omit the `rest` parameter.
decompose!(rest => { my_u8: &mut u8 });

// (borrows from multiple decompose statements simultaneously)
assert_eq!((my_u32, my_u8), (&2, &mut 5));
assert_eq!(my_i32, &1);  // (remains valid!)
…in an expression producing a “rest” tuple:

Jump to the “summary” section

These forms are useful for passing context to a method while allowing you to decompose the remainder while the borrow is still ongoing.

use compost::decompose;

#[derive(Debug)]
struct MyThing1<'a>(&'a mut i32, &'a mut u32);

impl<'a> MyThing1<'a> {
    fn new((a, b): (&'a mut i32, &'a mut u32)) -> Self {
        Self(a, b)
    }
}

#[derive(Debug)]
struct MyThing2<'a>(&'a mut char);

impl<'a> MyThing2<'a> {
    fn new((c,): (&'a mut char,)) -> Self {
        Self(c)
    }
}

fn do_something(mut cx: (&mut i32, &mut u32, &mut char, &str, &mut u8)) {
    // NOTE: Because `rest`'s tuple layout is unspecified, `rest` is new-typed in a
    // macro-internal `TupleRemainder` struct to allow for backwards-compatibility-
    // preserving changes to the maximum arity, the search mechanism, etc.
    let (ctor_args, mut cx) = decompose!(...cx);
    let thing_1 = MyThing1::new(ctor_args);

    let (ctor_args, mut cx) = decompose!(...cx);
    let thing_2 = MyThing2::new(ctor_args);

    dbg!(&thing_1);
    dbg!(&thing_2);

    // This syntax can also be combined with the type-annotated tuple syntax.
    let (the_str, mut cx) = decompose!(...cx => (&str));
    dbg!(the_str);

    let the_u8 = decompose!(cx => (&u8));
    dbg!(the_u8);
}

do_something((&mut 1, &mut 2, &mut 'c', "d", &mut 5));
…in an expression decomposing TupleRemainder:

Jump to the “summary” section

This one’s a bit wacky. Recall how rest tuples aren’t actually tuples but rather macro-internal TupleRemainder newtypes that should not, under any circumstance, be tampered with manually? Well, this form of the macro provides a way to safely tamper with these newtypes.

use compost::decompose;

let mut cx = (1i32, &2u32, &mut 3u8, 'd', "e");

decompose!(cx => rest & { my_char: &char });

// The type of `rest` is a `TupleRemainder<(...)>` where the type parameter of this
// newtype is an implementation detail. Let's change that!
let rest = decompose!(rest => (&mut ...));

// As the syntax suggests, the type of rest is now...
let rest: (&'_ mut i32, &mut &u32, &mut &mut u8, &mut &str) = rest;
//          ^ these lifetimes are the lifetime  ^ also notice how "&mut char"
//            of the mutable reference to the     is missing but the order is
//            input `rest` variable.              otherwise preserved?

let mut cmp_u8 = &mut 3u8;  // (done because `&mut T` forces `T` to be invariant
                            //  w.r.t its lifetime, causing weird type coercions
                            // and lifetime errors)
assert_eq!(my_char, &'d');
assert_eq!(rest, (&mut 1i32, &mut &2u32, &mut cmp_u8, &mut "e"));

// Of course, as a gesture of kindness towards other macro authors, this macro
// works for regular tuples as well:
let cx = decompose!(cx => (&mut ...));
let cx: (&'_ mut i32, &mut &u32, &mut &mut u8, &mut char, &mut &str) = cx;
//                                             ^ our "&mut char" is back!

The tuple returned from this form cannot be used in a fashion identical to its soure tuple. Recall: every element in the tuple now has an additional &mut indirection to it.

So, this doesn’t work:

use compost::decompose;

let mut cx = (&1i32, &2u32);
decompose!(cx => { my_i32: &i32, my_u32: &u32 });  // works!

let mut cx = decompose!(cx => (&mut ...));
decompose!(cx => { my_i32: &i32, my_u32: &u32 });  // doesn't work!

But this does:

use compost::decompose;

let mut cx = (&1i32, &2u32);
decompose!(cx => { my_i32: &i32, my_u32: &u32 });  // works!

let mut cx = decompose!(cx => (&mut ...));
decompose!(cx => { my_i32: &mut &i32, my_u32: &&u32 });  // also works but is different!
…in a combination of (most of) them:

Jump to the “summary” section

use compost::decompose;

struct MyThing {
    ...
}

impl MyThing {
    pub fn do_something<'a>(&mut self, deps: (&'a u32, &'a mut i32, &'a char)) -> &'a char {
        dbg!(&deps);
        deps.2
    }

    pub fn do_something_else(&mut self, deps: (&u8,)) {
        dbg!(deps);
    }
}

fn do_something(mut cx: (&mut MyThing, &mut u32, &mut i32, char, u8)) {
    // Acquire a reference to the `MyThing` instance.
    decompose!(cx => cx_rest & { thing: &mut MyThing });

    // Call a method on it with even more context.
    let (args, mut cx_rest) = decompose!(...cx_rest);
    let my_char = thing.do_something(args);

    // Call another unrelated method without rest decomposition.
    thing.do_something_else(decompose!(cx_rest));

    // `my_char` remains valid!
    dbg!(my_char);
}

Summary

In summary, here are the expression forms available for this macro:

  • decompose!($expr) -> (T1, T2, T3):
    Decomposes a tuple into a subset tuple.

  • decompose!($expr => ($T1, $T2, $T3)) -> (T1, T2, T3):
    Decomposes a tuple into a subset tuple with explicit type annotations.

Here are the expression with rest forms available for this macro:

  • decompose!(...$expr) -> ((T1, T2, T3), TupleRemainder<_>):
    Decomposes a tuple into a subset tuple and the remainder of the input tuple after the borrow.

  • decompose!(...$expr => ($T1, $T2, $T3)) -> ((T1, T2, T3), TupleRemainder<_>):
    Decomposes a tuple into a subset tuple and the remainder of the input tuple after the borrow with explicit type annotations.

Here is the one form to help you decompose a TupleRemainder into its actual components:

  • decompose!($expr => (&mut ...)) -> (&mut T1, &mut &T2, &mut &mut T3):
    Given a mutable reference to a tuple (newtyped through TupleRemainder or otherwise), it decomposes the input tuple into a new tuple with a mutable reference to every component in that tuple.

And here are its statement forms:

  • decompose!($expr => { $var_1: $T1, $var_2: $T2, $var_3: $T3 });:
    Decomposes a tuple into n different components and brings them in scope under the specified names.

  • decompose!($expr => $rest_name & { $var_1: $T1, $var_2: $T2, $var_3: $T3 });:
    Decomposes a tuple into n different components and brings them in scope under the specified names. Brings the remainder of the input tuple into scope under the specified $rest_name.

What Can Be Borrowed?

Rule 1

decompose! expects a mutable reference to the tuple it is decomposing. Thus, this is not valid:

use compost::decompose;

fn example(cx: (&i32, &u32)) {
    decompose!(cx => { my_i32: &i32 });
    dbg!(my_i32);
}

but this is:

use compost::decompose;

fn example(mut cx: (&i32, &u32)) {  // (see how the `cx` variable itself is now mut?)
    decompose!(cx => { my_i32: &i32 });
    dbg!(my_i32);
}
Rule 2

decompose! always decomposes tuples into tuples, even if they’re single element tuples.

Thus, this is not valid:

use compost::decompose;

fn takes_cx(cx: &i32) {
    dbg!(cx);
}

fn example(mut cx: (&i32, &u32)) {
    takes_cx(decompose!(cx));
}

…but this is:

use compost::decompose;

fn takes_cx(cx: (&i32,)) {  // (notice the trailing comma?)
    dbg!(cx);
}

fn example(mut cx: (&i32, &u32)) {
    takes_cx(decompose!(cx));
}

Note the trailing comma in (&i32,), which differentiates single element tuples from grouping parentheses around types.

Rule 3

Components in the input tuple can be anything. They can be references, mutable references, smart pointers, owned instances, etc. However, components in the output tuple must be immutable or mutable references.

A reference can be decomposed from an input tuple if the input tuple has some element that implements Borrow<T> (or BorrowMut<T> if the reference being requested is mutable) to that specific type T.

use core::borrow::Borrow;
use compost::decompose;

fn example<T: Borrow<V>, V>(mut cx: (T,)) {
    decompose!(cx => { v: &V });

    // Of course, you can still borrow the original value as well...
    decompose!(cx => { v: &mut T });
}

Note that the actual element itself must implement Borrow so, while T: Borrow<V>— making it possible to decompose &V from an owned instance of T&'_ T does not, making that decomposition invalid. You’d need to adjust your generic parameter bounds to make that work:

use core::borrow::Borrow;
use compost::decompose;

fn example<'a, T, V>(mut cx: (&'a mut T,))
where
    &'a mut T: Borrow<V>,
 {
    decompose!(cx => { v: &V });
}
Rule 4

The element of the tuple providing the appropriate Borrow implementation must be unambiguous. This implies, in the general case, that you cannot borrow an element when that element is present multiple times in the input tuple:

use compost::decompose;

fn example(mut cx: (&i32, &mut i32, &u32, Box<u32>)) {
    decompose!(cx => {
        my_i32: &i32,
        my_u32: &u32,
    });
    dbg!((my_i32, my_u32));
}

Funnily enough, this works:

use compost::decompose;

fn example(mut cx: (&i32, &mut i32, &u32, Box<u32>, &char, &char, &char)) {
    decompose!(cx => {
        // There's only one element in the input tuple that can give a **mutable
        // reference** to these respective elements.
        my_i32: &mut i32,
        my_u32: &mut u32,

        // Also, even though `&char` shows up *thrice* in the context tuple, it
        // is not used anywhere in the decomposition so it is fine.
    });
    dbg!((my_i32, my_u32));
}
Rule 5

Finally, elements used in a tuple decomposition can only be used once, even if they could theoretically be shared.

use compost::decompose;

fn example(mut cx: (&i32, &u32)) {
    // This works well but...
    decompose!(cx => rest & { my_first_i32_ref: &i32 });

    // This fails!
    decompose!(rest => { my_second_i32_ref: &i32 });

    dbg!((my_first_i32_ref, my_second_i32_ref));
}

Caveats

Caveat 1: Because variadic tuples are not a thing yet, the maximum arity of (number of elements in) both the input and output tuples is 12. This value is configurable in the source code (see: src/generated/decompose.rs.jinja’s MAX_ARITY template variable).

Caveat 2: Because decompose! consumes a mutable reference to the tuple being decomposed:

  1. The tuple must be marked as mutable (but you already knew that).
  2. Tuple temporaries cannot be decomposed and returned from the function.

Thus, this fails to compile:

use compost::decompose;

fn give_me_some_things<'a>(mut cx: (&'a u32, &'a mut i32)) -> (&'a u32, &'a i32) {
    decompose!(cx)
}