Struggling with multiple mutable borrows

Can you please teach me what would be the correct, "Rust way" to achieve what I am trying to do?

My expectation is that:

  • I call a.find_foo_identifier
  • find_foo_identifier returns &str, and I expect this reference to be directly copied from the foo array, from the borrowed value from inside the Vec, which I got from iter() - in fact, I have this map call in which I do the dereference all the way down to &str.
  • And because of this copy, I expect that this should release a from being borrowed, because the data is copied from it.
  • And then I call push_foo., mutate a, all clean
  • And then I call push_bar , mutate a again, all good clean

What instead happens:

  • push_foo requires mutable borrow
  • push_bar requires mutable borrow
  • compiler doesn't allow me to have them at the same time
  • and find_foo_identifier does an immutable borrow, and it's not compatible with either of the mutable borrows.

It's as if everything is just in a permanent borrowed state, shouldn't it be, like, released after the reference is done being used?

I am observing an error at a.push_foo("very good");. It says: "cannot borrow a as mutable because it is also borrowed as immutable"

Question: how can I achieve what I'm trying to do in the proper Rust way, so that the borrow checker is satisfied?

Code in question:

struct A<'a> {
    foo: Vec<&'a str>,
    bar: Vec<&'a str>,
}

impl<'a> A<'a> {
    pub fn find_foo_identifier(&self, identifier: &str) -> Option<&str> {
        self.foo.iter()
            .find(|local_node| **local_node == identifier)
            .map(|s|*s)
    }
    fn push_foo(&mut self, s: &'a str) {
        self.foo.push(s);
    }
    fn push_bar(&mut self, s: &'a str) {
        self.bar.push(s);
    }
}

fn build_a<'a>() -> A<'a> {
    let mut a = A { foo: vec![], bar: vec![] };

    for _ in 0..20 {
        let found = a.find_foo_identifier("asdf");

        if let Some(s) = found {
            a.push_foo("very good");
            a.push_bar(s);
        }
    }

    a
}

i think that you need the same lifetime when extract the find:

impl<'a> A<'a> {
    pub fn find_foo_identifier(&self, identifier: &str) -> Option<&'a str> {
        self.foo
            .iter()
            .find(|&local_node| *local_node == identifier)
            .map(|s| *s)
    }

    fn push_foo(&mut self, s: &'a str) {
        self.foo.push(s);
    }

    fn push_bar(&mut self, s: &'a str) {
        self.bar.push(s);
    }
}
4 Likes

You might not want to have Vec<&'a str>s in the first place. Non-'static references are generally for short lived borrows, not long lived data structures.

But, that's not what you asked about.

When you do use shared-borrow-managing structs like A<'a>, and you want to return a borrow, you're typically going to want to return a borrow with the lifetime 'a:

-    pub fn find_foo_identifier(&self, identifier: &str) -> Option<&str> {
+    pub fn find_foo_identifier(&self, identifier: &str) -> Option<&'a str> {

If you have other methods that return your managed borrows, you should check that they also return 'a borrows. It's easy to miss, since function lifetime elision works against you for this use case.

As you can see in the link, without the change the method signature desugars to:

pub fn find_foo_identifier<'s, 'i>(
    &'s self,           // <---+
    identifier: &'i str //     | Same lifetime
) -> Option<&'s str> {  // <---+

And the meaning of this method API is "so long as the returned value is in use, keep this A<'a> borrowed." That API is enforced at the call site, even if the method body doesn't need the capability. The lack of implementation detail leakage is an intentional design of the language; you could think of it as the compiler enforcing the method body's right to actually return a borrow of *self at some later date, without breaking downstream.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.