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

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

提问人:some_engineer 提问时间:3/18/2023 最后编辑:some_engineer 更新时间:3/19/2023 访问量:131

问:

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

评论

1赞 cdhowie 3/18/2023
如果您要用其他东西替换字符串,则可以使用 和 现有的字符串。Vec<Cell<String>>.take()
0赞 Chayim Friedman 3/19/2023
@cdhowie 为什么是?Cell

答:

0赞 cafce25 3/18/2023 #1

你可以用这样的方式包装你的字符串,你可以便宜地拥有一些东西,这样你就不会引用原始结构:RccloneRc

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_mutRefCell


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

最直接的解决方案是删除调用的 from:StringFoobaz


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赞 some_engineer 3/19/2023
好。但是在 x 中已经有 &Rc<_> 的类型,所以不需要,只需 .或者更简单:..map(|x| Rc::clone(&x))&xx.map(Rc::clone)
0赞 1c982d 3/18/2023 #2

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

RefCell是我首先想到的,但我无法构建一个可行的例子,所以我删除了一段时间的答案,以避免误导其他人。我的错误是坚持方法的论证,这完全浪费了力量。&mut selfRefCell

#![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`
    }
}

避免此问题的另一种方法是使用索引而不是引用。索引(无符号整数)可以复制,因此您不会被借用检查。编译器资源管理器链接: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.
    }
}