如何解决可变借用和不可变借用的共存问题?
How to workaround the coexistence of a mutable and immutable borrow?
我有一个 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);
}
}
在一个函数中,我需要先获取 context
的 name
并根据 name
更新 foo
我有:
let name = context.get_name();
if (name == "foo") {
context.set_foo(4);
}
代码无法编译,因为 get_name()
需要 &self
而 set_foo()
需要 &mut self
。换句话说,我有一个 get_name()
的不可变借用,但我在同一范围内也有 set_foo()
的可变借用,这与 the rules of references 相悖。
At any given time, you can have either (but not both of) one mutable reference or any number of immutable 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 语句中过期。这 通常 是你想要的方式,并且随着非词汇生命周期等功能变得更好,这个选项变得更可口。例如,由于此构造被正确检测为 "limited borrow",因此您当前编写的方式将在 NLL 可用时起作用!重新制定有时会因奇怪的原因而失败(特别是如果语句需要可变和不可变调用的结合),但应该是您的第一选择。
二.使用表达式作为语句的范围黑客
let name_is_foo = {
let name = context.get_name();
name == "foo"
};
if name_is_foo {
context.set_foo(4);
}
Rust 使用 return 值的任意范围语句的能力令人难以置信 强大。如果其他一切都失败了,你可以 almost 始终使用块来限制你的借用,并且只 return 一个非借用标志值,然后你将其用于你的可变调用。通常使用 I. 方法会更清楚,但这个方法很有用、清晰且符合 Rust.
三。在类型
上创建一个 "fused method"
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>
,并根据闭包的 return 值进行设置(None
for "don't set",Some(val)
设置那个瓦尔)。
有时最好允许结构自行修改而不执行逻辑 "outside"。这对树来说尤其如此,但在最坏的情况下可能会导致方法爆炸,当然如果在你无法控制的外部类型上操作是不可能的。
四.克隆
let name = context.get_name().clone();
if name == "foo" {
context.set_foo(4);
}
有时您必须进行快速克隆。尽可能避免这种情况,但有时值得在某个地方输入 clone()
而不是花 20 分钟来弄清楚到底如何让你的借用工作。取决于您的截止日期、克隆的成本、调用该代码的频率等等。
例如,可以说在 CLI 应用程序中过度克隆 PathBuf
s 并不少见。
伏。使用不安全(不推荐)
let name: *const str = context.get_name();
unsafe{
if &*name == "foo" {
context.set_foo(4);
}
}
这几乎不应该被使用,但在极端情况下可能是必要的,或者在您基本上被迫克隆的情况下为了性能(这可能发生在图形或一些不稳定的数据结构中)。始终,始终尽最大努力避免这种情况,但将其保存在您的工具箱中以防万一。
请记住,编译器希望您编写的不安全代码支持安全 Rust 代码所需的所有保证。 unsafe
块表示虽然编译器无法验证代码是否安全,但程序员可以。如果程序员没有正确地验证它,编译器很可能会产生包含未定义行为的代码,这会导致内存不安全、崩溃等,Rust 力求做到的很多事情避免。
可能有一些答案已经回答了你,但有很多情况会触发此错误消息,所以我会回答你的具体情况。
更简单的解决方案是使用 #![feature(nll)]
,这样编译没有问题。
但是您可以在没有 nll 的情况下解决问题,方法是使用像这样的简单匹配:
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
match context.get_name() {
"foo" => context.set_foo(4),
// you could add more case as you like
_ => (),
}
}
在看到@Stargateur 的评论之前,我想到了下面的代码,它编译得很好,但确实克隆了名称字符串:
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> String {
self.name.clone()
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: String::from("bill"),
foo: 5,
};
let name = context.get_name();
if name == "foo" {
context.set_foo(4);
}
println!("Hello World!");
}
使用@Stargateur 的示例,结果证明这个特定问题有一个非常简单的解决方案 - 结合 get_name
和 if
,例如
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> &String {
&self.name
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
if context.get_name() == "foo" {
context.set_foo(4);
}
}
我认为这是因为 get_name
部分的变量已经清楚地描述了它的生命周期,而当 name
变量是单独的时,它的值基本上可以在没有明确说明的情况下突然改变突变。
我有一个 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);
}
}
在一个函数中,我需要先获取 context
的 name
并根据 name
更新 foo
我有:
let name = context.get_name();
if (name == "foo") {
context.set_foo(4);
}
代码无法编译,因为 get_name()
需要 &self
而 set_foo()
需要 &mut self
。换句话说,我有一个 get_name()
的不可变借用,但我在同一范围内也有 set_foo()
的可变借用,这与 the rules of references 相悖。
At any given time, you can have either (but not both of) one mutable reference or any number of immutable 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 语句中过期。这 通常 是你想要的方式,并且随着非词汇生命周期等功能变得更好,这个选项变得更可口。例如,由于此构造被正确检测为 "limited borrow",因此您当前编写的方式将在 NLL 可用时起作用!重新制定有时会因奇怪的原因而失败(特别是如果语句需要可变和不可变调用的结合),但应该是您的第一选择。
二.使用表达式作为语句的范围黑客
let name_is_foo = {
let name = context.get_name();
name == "foo"
};
if name_is_foo {
context.set_foo(4);
}
Rust 使用 return 值的任意范围语句的能力令人难以置信 强大。如果其他一切都失败了,你可以 almost 始终使用块来限制你的借用,并且只 return 一个非借用标志值,然后你将其用于你的可变调用。通常使用 I. 方法会更清楚,但这个方法很有用、清晰且符合 Rust.
三。在类型
上创建一个 "fused method" 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>
,并根据闭包的 return 值进行设置(None
for "don't set",Some(val)
设置那个瓦尔)。
有时最好允许结构自行修改而不执行逻辑 "outside"。这对树来说尤其如此,但在最坏的情况下可能会导致方法爆炸,当然如果在你无法控制的外部类型上操作是不可能的。
四.克隆
let name = context.get_name().clone();
if name == "foo" {
context.set_foo(4);
}
有时您必须进行快速克隆。尽可能避免这种情况,但有时值得在某个地方输入 clone()
而不是花 20 分钟来弄清楚到底如何让你的借用工作。取决于您的截止日期、克隆的成本、调用该代码的频率等等。
例如,可以说在 CLI 应用程序中过度克隆 PathBuf
s 并不少见。
伏。使用不安全(不推荐)
let name: *const str = context.get_name();
unsafe{
if &*name == "foo" {
context.set_foo(4);
}
}
这几乎不应该被使用,但在极端情况下可能是必要的,或者在您基本上被迫克隆的情况下为了性能(这可能发生在图形或一些不稳定的数据结构中)。始终,始终尽最大努力避免这种情况,但将其保存在您的工具箱中以防万一。
请记住,编译器希望您编写的不安全代码支持安全 Rust 代码所需的所有保证。 unsafe
块表示虽然编译器无法验证代码是否安全,但程序员可以。如果程序员没有正确地验证它,编译器很可能会产生包含未定义行为的代码,这会导致内存不安全、崩溃等,Rust 力求做到的很多事情避免。
可能有一些答案已经回答了你,但有很多情况会触发此错误消息,所以我会回答你的具体情况。
更简单的解决方案是使用 #![feature(nll)]
,这样编译没有问题。
但是您可以在没有 nll 的情况下解决问题,方法是使用像这样的简单匹配:
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
match context.get_name() {
"foo" => context.set_foo(4),
// you could add more case as you like
_ => (),
}
}
在看到@Stargateur 的评论之前,我想到了下面的代码,它编译得很好,但确实克隆了名称字符串:
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> String {
self.name.clone()
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: String::from("bill"),
foo: 5,
};
let name = context.get_name();
if name == "foo" {
context.set_foo(4);
}
println!("Hello World!");
}
使用@Stargateur 的示例,结果证明这个特定问题有一个非常简单的解决方案 - 结合 get_name
和 if
,例如
struct Context {
name: String,
foo: i32,
}
impl Context {
fn get_name(&self) -> &String {
&self.name
}
fn set_foo(&mut self, num: i32) {
self.foo = num
}
}
fn main() {
let mut context = Context {
name: "MisterMV".to_owned(),
foo: 42,
};
if context.get_name() == "foo" {
context.set_foo(4);
}
}
我认为这是因为 get_name
部分的变量已经清楚地描述了它的生命周期,而当 name
变量是单独的时,它的值基本上可以在没有明确说明的情况下突然改变突变。