不能借用`*self`。如何在没有字符串克隆的情况下使 fn 工作?

问题描述 投票:0回答:2
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

我不想每次都克隆一个字符串。如何让它与借来的字符串一起工作?

是的,我真的需要这里的

&mut self

rust borrow-checker borrow
2个回答
0
投票

您可以将字符串包裹在

Rc
中,这样您就可以廉价地
clone
Rc
并拥有一些东西,这样您就不会引用原始结构:

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());
        }
    }
    // …
}

对于底层字符串的可变访问,您可以使用

Rc::get_mut
或进一步包装在
RefCell
或类似的。


原始未指定问题的解决方案:

最直接的解决方案是从

String
中删除
Foo
以进行
baz
调用:


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

-1
投票

Cell
RefCell
旨在解决内部可变性问题
Cell
通过
Copy
特征解决这个问题,而
RefCell
通过间接解决这个问题。通过将所有数据包装在
RefCell
中,方法的参数中只需要
&self
s,您仍然可以通过在
borrow_mut()
上调用
RefCell
来改变堆栈。

RefCell
是我第一个想到的,但是我无法构建一个可行的例子,所以我删除了一段时间的答案以避免误导他人。我的错误是将
&mut self
保留在方法的参数中,这完全浪费了
RefCell
的力量。

#![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) {
        self.data.borrow_mut().stack.push("46".to_string());
    }
}

另一种避免这个问题的方法是使用索引而不是引用。索引(无符号整数)可以被复制,所以你不会被借用检查。编译器资源管理器链接: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.
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.