Mutable borrow into two parts with cleanup

Viewed 163

I have some object that I want to split into two parts via a mutable borrow, then combine those back together into the original object when the split references go out of scope.

The simplified example below is for a Count struct that holds a single i32, which we want to split into two &mut i32s, who are both incorporated back into the original Count when the two mutable references go out of scope.

The approach I am taking below is to use an intermediate object CountSplit which holds a mutable reference to the original Count object and has the Drop trait implemented to do the re-combination logic.

This approach feels kludgy. In particular, this is awkward:

let mut ms = c.make_split();
let (x, y) = ms.split();

Doing this in one line like let (x, y) = c.make_split().split(); is not allowed because the intermediate object must have a longer lifetime. Ideally I would be able to do something like let (x, y) = c.magic_split(); and avoid exposing the intermediate object altogether.

Is there a way to do this which doesn't require doing two let's every time, or some other way to tackle this pattern that would be more idiomatic?

#[derive(Debug)]
struct Count {
    val: i32,
}

trait MakeSplit<'a> {
    type S: Split<'a>;
    fn make_split(&'a mut self) -> Self::S;
}

impl<'a> MakeSplit<'a> for Count {
    type S = CountSplit<'a>;
    fn make_split(&mut self) -> CountSplit {
        CountSplit {
            top: self,
            second: 0,
        }
    }
}

struct CountSplit<'a> {
    top: &'a mut Count,
    second: i32,
}

trait Split<'a> {
    fn split(&'a mut self) -> (&'a mut i32, &'a mut i32);
}

impl<'a, 'b> Split<'a> for CountSplit<'b> {
    fn split(&mut self) -> (&mut i32, &mut i32) {
        (&mut self.top.val, &mut self.second)
    }
}

impl<'a> Drop for CountSplit<'a> {
    fn drop(&mut self) {
        println!("custom drop occurs here");
        self.top.val += self.second;
    }
}

fn main() {
    let mut c = Count { val: 2 };
    println!("{:?}", c); // Count { val: 2 }

    {
        let mut ms = c.make_split();
        let (x, y) = ms.split();
        println!("split: {} {}", x, y); // split: 2 0

        // each of these lines correctly gives a compile-time error
        // c.make_split();         // can't borrow c as mutable
        // println!("{:?}", c);    //                   or immutable
        // ms.split();             // also can't borrow ms

        *x += 100;
        *y += 5000;
        println!("split: {} {}", x, y); // split: 102 5000
    } // custom drop occurs here

    println!("{:?}", c); // Count { val: 5102 }
}

playground:

1 Answers

I don't think a reference to a temporary value like yours can be made to work in today's Rust.

If it's any help, if you specifically want to call a function with two &mut i32 parameters like you mentioned in the comments, e.g.

fn foo(a: &mut i32, b: &mut i32) {
    *a += 1;
    *b += 2;
    println!("split: {} {}", a, b);
}

you can already do that with the same number of lines as you'd have if your chaining worked.

With the chaining, you'd call

let (x, y) = c.make_split().split();
foo(x, y);

And if you just leave out the conversion to a tuple, it looks like this:

let mut ms = c.make_split();
foo(&mut ms.top.val, &mut ms.second);

You can make it a little prettier by e.g. storing the mutable reference to val directly in CountSplit as first, so that it becomes foo(&mut ms.first, &mut ms.second);. If you want it to feel even more like a tuple, I think you can use DerefMut to be able to write foo(&mut ms.0, &mut ms.1);.

Alternatively, you can of course formulate this as a function taking a function

impl Count {
    fn as_split<F: FnMut(&mut i32, &mut i32)>(&mut self, mut f: F) {
        let mut second = 0;
        f(&mut self.val, &mut second);
        self.val += second;
    }
}

and then just call

c.as_split(foo);
Related