我有一个 Context 结构:

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> &str {
        &self.name
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    let name = context.get_name();
    if name == "foo" {
        context.set_foo(4);
    }
}

在一个函数中,我需要先获取namecontext并根据我拥有的foo更新name:
let name = context.get_name();
if (name == "foo") {
    context.set_foo(4);
}

该代码无法编译,因为get_name()接受&selfset_foo()接受&mut self。换句话说,我对 get_name() 有一个不可变的借用,但我在同一范围内也有 set_foo() 的可变借用,这与 the rules of references 相悖。



错误看起来像:

error[E0502]: cannot borrow `context` as mutable because it is also borrowed as immutable
  --> src/main.rs:22:9
   |
20 |     let name = context.get_name();
   |                ------- immutable borrow occurs here
21 |     if name == "foo" {
22 |         context.set_foo(4);
   |         ^^^^^^^ mutable borrow occurs here
23 |     }
24 | }
   | - immutable borrow ends here

我想知道如何解决这种情况?

最佳答案

这是一个非常广泛的问题。借用检查器可能是 Rust 最有用的功能之一,但也是最难处理的。人体工程学的改进正在定期进行,但有时会发生这样的情况。

有几种方法可以解决这个问题,我将尝试讨论每种方法的优缺点:

一、转换为只需要有限借用的形式

当你学习 Rust 时,你会慢慢了解借用何时到期以及多快。例如,在这种情况下,您可以转换为

if context.get_name() == "foo" {
    context.set_foo(4);
}

借用在 if 语句中到期。这个 通常 是你想要的方式,并且随着诸如非词法生命周期之类的特性变得更好,这个选项变得更加可口。例如,由于此构造被正确检测为“有限借用”,因此当 NLL 可用时,您当前编写它的方式将起作用!重新制定有时会因奇怪的原因而失败(特别是如果语句需要可变和不可变调用的结合),但应该是您的首选。

二、将范围限定技巧与表达式作为语句一起使用
let name_is_foo = {
    let name = context.get_name();
    name == "foo"
};

if name_is_foo {
    context.set_foo(4);
}

Rust 使用任意范围的返回值的语句的能力是 令人难以置信的 强大。如果其他一切都失败了,您几乎总是可以使用块来限制您的借用范围,并且只返回一个非借用标志值,然后您将其用于可变调用。使用方法 I. 在可用时通常更清晰,但这个是有用的、清晰的、惯用的 Rust。

三、在类型上创建“融合方法”
   impl Context {
      fn set_when_eq(&mut self, name: &str, new_foo: i32) {
          if self.name == name {
              self.foo = new_foo;
          }
      }
   }

当然,这有无穷无尽的变化。最通用的是一个函数,它采用 fn(&Self) -> Option<i32> ,并根据该闭包的返回值进行设置(None 表示“不设置”,Some(val) 表示设置该 val)。

有时最好允许结构自行修改,而不要在“外部”执行逻辑。这对于树尤其如此,但在最坏的情况下会导致方法爆炸,当然如果在您无法控制的外来类型上操作,这是不可能的。

四、克隆
let name = context.get_name().clone();
if name == "foo" {
    context.set_foo(4);
}

有时您必须进行快速克隆。尽可能避免这种情况,但有时只在某处输入 clone() 而不是花 20 分钟试图弄清楚如何使您的借用工作是值得的。取决于您的截止日期、克隆的成本、您调用该代码的频率等等。

例如,可以说在 CLI 应用程序中过度克隆 PathBuf 并不少见。

五、使用不安全(不推荐)
let name: *const str = context.get_name();
unsafe{
    if &*name == "foo" {
        context.set_foo(4);
    }
}

这几乎永远不应该使用,但在极端情况下可能是必要的,或者在您基本上被迫克隆的情况下(这可能发生在图形或一些不稳定的数据结构中)的性能。始终,始终尽最大努力避免这种情况,但请将其保存在您的工具箱中,以防万一。

请记住,编译器希望您编写的不安全代码支持安全 Rust 代码所需的所有保证。 unsafe 块表示虽然编译器无法验证代码是否安全,但程序员可以。如果程序员没有正确验证它,编译器很可能会产生包含未定义行为的代码,这会导致内存不安全、崩溃等,这是 Rust 努力避免的许多事情。

关于rust - 如何解决可变借用和不可变借用共存的问题?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52709147/

10-12 06:54