Returning references from an impl that know they're from the same source?

Recently I was faced with a simple problem where I had a struct with two vecs within, and I wanted to filter one against the other. I wrote a simple call to retain and used direct references to the attributes of the struct. It all more or less worked.

I then had the need to slightly abstract what I was doing and try to use a function that provides the vectors. In the simplest case, its just a wrapper around the same struct, so the references are ultimately pointing back to the same thing. This ends up not working because I am doing two different borrows. What I'm wondering is if there's some way to mark this up in a way that lets the borrow checker behave the same as the direct attribute references?

I tried to make a small reproduction (playground):

#[derive(Debug)]
struct Wrapper {
    thing1: Vec<u32>,
    thing2: Vec<u32>,
}

impl Wrapper {
    fn get_thing1(&self) -> &Vec<u32> {
        &self.thing1
    }

    fn get_thing2(&mut self) -> &mut Vec<u32> {
        &mut self.thing2
    }
}

fn de_dupe(w: &mut Wrapper) {
    let thing1 = w.get_thing1();
    let thing2 = w.get_thing2();
    thing2.retain(|v| thing1.contains(v));
}

fn de_dupe_without_impl(w: &mut Wrapper) {
    w.thing2.retain(|v| w.thing1.contains(v));
}

fn main() {
    let mut w = Wrapper {
        thing1: vec![1, 2, 3, 4, 5],
        thing2: vec![4, 5, 6, 7],
    };

    de_dupe(&mut w);

    de_dupe_without_impl(&mut w);

    dbg!(w);
}

This generates:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `*w` as mutable because it is also borrowed as immutable
  --> src/main.rs:19:18
   |
18 |     let thing1 = w.get_thing1();
   |                  -------------- immutable borrow occurs here
19 |     let thing2 = w.get_thing2();
   |                  ^^^^^^^^^^^^^^ mutable borrow occurs here
20 |     thing2.retain(|v| thing1.contains(v));
   |                       ------ immutable borrow later captured here by closure

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` (bin "playground") due to previous error

My actual use case has a slightly more involved check than the contains used here, but ultimately it just boils down to "i need a variable from thing2 and all of thing1".

Any hints/tips? I'm sure theres some alternative way to write the logic that could avoid this scenario outright, too.

I'm sure I'm using the wrong terminology here, so I'd love to learn what the proper terms for some of these concepts are, too.

What you are looking for are called splitting borrows. I believe the easiest solution would be to have a single method to retrieve both vectors (that way the borrow checker is able to understand that there are no overlapping mutable references):

#[derive(Debug)]
struct Wrapper {
    thing1: Vec<u32>,
    thing2: Vec<u32>,
}

impl Wrapper {
    fn get_things_mut(&mut self) -> (&mut Vec<u32>, &mut Vec<u32>) {
        (&mut self.thing1, &mut self.thing2)
    }
}

fn de_dupe(w: &mut Wrapper) {
    let (thing1, thing2) = w.get_things_mut();
    
    thing2.retain(|v| thing1.contains(v));
}

fn main() {
    let mut w = Wrapper {
        thing1: vec![1, 2, 3, 4, 5],
        thing2: vec![4, 5, 6, 7],
    };

    de_dupe(&mut w);
    
    dbg!(w);
}

Playground.

Just as a nit, in Rust you'll see often times that methods that return a mutable reference are denoted with a _mut at the end of the method's name.

6 Likes

Not really. I believe the feature you want is called "substructural borrows", which Rust doesn't (and might never) have.

There are a few workarounds you can use. First, you can have a method take both borrows at the same time:

fn split_things(w: &mut Wrapper) -> (&Vec<u32>, &mut Vec<u32>) {
    (&w.thing1, &mut w.thing2)
}

This works because the compiler knows that those two field accesses are disjoint, and thus don't overlap.

The other is to do what I call "renting". It's like borrowing but different:

fn de_dupe(w: &mut Wrapper) {
    let thing1 = std::mem::replace(&mut w.thing1, vec![]);
    let thing2 = w.get_thing2();
    thing2.retain(|v| thing1.contains(v));
    w.thing1 = thing1;
}

(You could move those two lines into methods; I didn't because I'm too lazy to fire up a code editor right now for proper formatting...)

The idea here is to "disconnect" thing1 from the rest of the Wrapper while you manipulate thing2, then put it back. The catches with this are that you have to remember to put the rented value back, and it will make any method that looks at or manipulates thing1 behave strangely.

(In some cases where this process is designed into the type, I'll have the fields actually be Option<T> instead, using None to represent the "this is rented, don't use right now" case for runtime error detection.)

2 Likes
4 Likes