Cannot borrow `*self`. How to make the fn work without string cloning?

145 Views Asked by At
struct Foo {
    stack: Vec<String>,
}

impl Foo {
    pub fn bar(&mut self) {
        // find condition here is for example only.
        // position in the stack is important.
        if let Some(s) = self.stack.iter().find(|x| x.is_ascii()) {
            self.baz(s.as_str());
        }
    }

    fn baz(&mut self, _input: &str) {
       // mutating of `self.stack` and some other fields.
    }
}
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
 --> src/main.rs:8:13
  |
7 |         if let Some(s) = self.stack.last() {
  |                          ----------------- immutable borrow occurs here
8 |             self.baz(s.as_str());
  |             ^^^^^---^^^^^^^^^^^^
  |             |    |
  |             |    immutable borrow later used by call
  |             mutable borrow occurs here

I don't want to clone a string every time. How to get it working with borrowed string?

And yes, I really need the &mut self here.

2

There are 2 best solutions below

1
cafce25 On BEST ANSWER

You can wrap your strings in Rc that way you can cheaply clone the Rc and have something owned so you don't reference the original struct:

use std::rc::Rc;
struct Foo {
    stack: Vec<Rc<String>>,
}

impl Foo {
    pub fn bar(&mut self) {
        if let Some(s) = self
            .stack
            .iter()
            .find(|x| x.is_ascii())
            .map(Rc::clone)
        {
            self.baz(s.as_str());
        }
    }
    // …
}

For mutable access to the underlying strings you can use Rc::get_mut or wrap further in RefCell or similar.


Solution to the original underspecified question:

The most straight forward solution is to just remove the String from Foo for the baz call:


struct Foo {
    stack: Vec,
}

impl Foo {
    pub fn bar(&mut self) {
        if let Some(s) = self.stack.pop() {
            self.baz(s.as_str());
            self.stack.push(s);
        }
    }

    fn baz(&mut self, _input: &str) {}
}
0
1c982d On

Cell and RefCell are designed to solve interior mutability problems. Cell sloves this by Copy trait, while RefCell solves this by an indirection. By wrapping all the data inside a RefCell, only &selfs are needed in methods' arguments, and you can still mutate the stack by calling borrow_mut() on RefCell.

RefCell was the first thing I thought of, but I wasn't able to construct a viable example, so I deleted the answer for some time to avoid misleading others. My mistake was to keep &mut self in methods' arguments, which totally wasted RefCell's power.

#![allow(dead_code, unused_variables, unused_imports)]

struct FooImpl {
    stack: Vec<String>,
}

struct Foo {
    data: std::cell::RefCell<FooImpl>,
}

impl Foo {
    pub fn bar(&self) {
        if let Some(s) = self.data.borrow().stack.iter().find(|x| x.is_ascii()) {
            self.baz(s.as_str());
        }
    }

    fn baz(&self, _input: &str) {
        self.mutate();
    }

    fn mutate(&self) {
        // You can mutate fields other than `stack`
    }
}

Another way to avoid this problem is to use index instead of reference. Indices (which are unsigned integers) can be copied, so you don't get borrow checked. Compiler explorer link: https://godbolt.org/z/chWc5G3zK

#![allow(dead_code, unused_variables, unused_imports)]

struct Foo {
    stack: Vec<String>,
}

impl Foo {
    pub fn bar(&mut self) {
        // `position` returns the index, while `find` returns the reference.
        if let Some(index) = self.stack.iter().position(|x| x.is_ascii()) {
            //                                   ^^^
            // `index` is copied, so you avoid the borrow check and make the 
            // program a bit more "unsafe". Since `index` is returned by
            // `find`, it should be okay.

            // Option 1: take the string, call `baz` and put the string back.
            let value = std::mem::take(&mut self.stack[index]);
            self.baz(value.as_str());
            let _ = std::mem::replace(&mut self.stack[index], value);

            // Option 2: create another function which takes `&mut self` 
            // along with an index.
            self.baz_with_index(index);
        }
    }

    fn baz(&mut self, _input: &str) {
        // mutating of `self.stack` and some other fields.
    }

    fn baz_with_index(&mut self, index: usize) {
        // mutating of `self.stack` and some other fields.
    }
}