some_engineer
some_engineer

Reputation: 1874

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

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.

Upvotes: -1

Views: 158

Answers (2)

1c982d
1c982d

Reputation: 474

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.
    }
}

Upvotes: 0

cafce25
cafce25

Reputation: 27218

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) {}
}

Upvotes: 0

Related Questions