为什么借用仍然保留在 if let 的 else 块中?
Why is a borrow still held in the else block of an if let?
为什么以下代码中的调用 self.f2()
会触发借用检查器? else 块不是在不同的范围内吗?这真是个难题!
use std::str::Chars;
struct A;
impl A {
fn f2(&mut self) {}
fn f1(&mut self) -> Option<Chars> {
None
}
fn f3(&mut self) {
if let Some(x) = self.f1() {
} else {
self.f2()
}
}
}
fn main() {
let mut a = A;
}
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:16:13
|
13 | if let Some(x) = self.f1() {
| ---- first mutable borrow occurs here
...
16 | self.f2()
| ^^^^ second mutable borrow occurs here
17 | }
| - first borrow ends here
self 的借用范围不是以 self.f1()
调用开始和结束的吗?一旦来自 f1()
的调用返回,f1()
不再使用 self,因此借用检查器应该不会对第二次借用有任何问题。请注意以下代码也失败了...
// ...
if let Some(x) = self.f1() {
self.f2()
}
// ...
我认为第二次借用在这里应该没问题,因为 f1
和 f3
没有与 f2
.
同时使用 self
可变引用是非常强有力的保证:只有一个指针指向特定的内存位置。既然你已经借了一个&mut
,你就不能再借了。这将在多线程上下文中引入数据竞争,并在单线程上下文中引入迭代器失效和其他类似问题。
现在,借用是基于词法作用域的,所以第一个借用一直持续到函数结束,句点。最终,我们希望放宽此限制,但这需要一些工作。
这很烦人,但您可以通过引入内部作用域并稍微更改控制流来解决此问题:
fn f3(&mut self) {
{
if let Some(x) = self.f1() {
// ...
return;
}
}
self.f2()
}
正如评论中所指出的,这不需要额外的大括号。这是因为 if
或 if...let
表达式具有隐式范围,并且借用持续该范围:
fn f3(&mut self) {
if let Some(x) = self.f1() {
// ...
return;
}
self.f2()
}
这是 Sandeep Datta 和 mbrubeck 之间的 IRC 聊天记录:
mbrubeck: std:tr::Chars contains a borrowed reference to the string that created it. The full type name is Chars<'a>
. So f1(&mut self) -> Option<Chars>
without elision is f1(&'a mut self) -> Option<Chars<'a>>
which means that self
remains borrowed as long as
the return value from f1
is in scope.
Sandeep Datta: Can I use 'b for self and 'a for Chars to avoid this problem?
mbrubeck: Not if you are actually returning an iterator over something from self
. Though if you can make a function from &self -> Chars
(instead of &mut self -> Chars
) that would fix the issue.
我在这里整理了一个示例来展示范围规则:
struct Foo {
a: i32,
}
impl Drop for Foo {
fn drop(&mut self) {
println!("Foo: {}", self.a);
}
}
fn generate_temporary(a: i32) -> Option<Foo> {
if a != 0 { Some(Foo { a: a }) } else { None }
}
fn main() {
{
println!("-- 0");
if let Some(foo) = generate_temporary(0) {
println!("Some Foo {}", foo.a);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(foo) = generate_temporary(1) {
println!("Some Foo {}", foo.a);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(Foo { a: 1 }) = generate_temporary(1) {
println!("Some Foo {}", 1);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(Foo { a: 2 }) = generate_temporary(1) {
println!("Some Foo {}", 1);
} else {
println!("None");
}
println!("-- 1");
}
}
这会打印:
-- 0
None
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
None
Foo: 1
-- 1
简而言之,if
子句中的表达式似乎同时存在于 if
块和 else
块中。
一方面这并不奇怪,因为它确实需要比 if
块寿命更长,但另一方面它确实阻止了有用的模式。
如果您更喜欢直观的解释:
if let pattern = foo() {
if-block
} else {
else-block
}
脱糖成:
{
let x = foo();
match x {
pattern => { if-block }
_ => { else-block }
}
}
虽然您希望它脱糖成:
bool bypass = true;
{
let x = foo();
match x {
pattern => { if-block }
_ => { bypass = false; }
}
}
if not bypass {
else-block
}
你不是第一个被这个绊倒的人,所以尽管改变了一些代码的含义(特别是守卫),这个问题可能会在某个时候得到解决。
这是消除虚假错误的方法。我是Rust新手所以以下解释可能有严重错误。
use std::str::Chars;
struct A<'a> {
chars: Chars<'a>,
}
这里的'a
是一个生命周期参数(就像C++中的模板参数)。在 Rust 中类型可以通过生命周期参数化。
Chars
类型也有一个生命周期参数。这意味着 Chars
类型可能有一个需要生命周期参数的成员元素。生命周期参数只对引用有意义(因为这里的生命周期实际上意味着 "lifetime of a borrow")。
我们知道 Chars
需要保留对创建它的字符串的引用,'a
可能用于表示源字符串的生命周期。
这里我们简单地提供 'a
作为 Chars
的生命周期参数,告诉 Rust 编译器 Chars
的生命周期与结构体 [=26= 的生命周期相同]. IMO "lifetime 'a of type A" 应读作 "lifetime 'a of the references contained in the struct A"。
我认为结构实现可以独立于结构本身进行参数化,因此我们需要使用 impl
关键字重复参数。这里我们将名称 'a 绑定到结构 A 的生命周期。
impl<'a> A<'a> {
在函数 f2
的上下文中引入了名称 'b
。这里用来绑定引用的生命周期&mut self
.
fn f2<'b>(&'b mut self) {}
名称'b
是在函数f1
的上下文中引入的。这个'b
与[=29引入的'b
没有直接关系=] 以上。
这里用来绑定引用的生命周期&mut self
。不用说这个引用也和之前函数中的&mut self
没有任何关系,这是对self
.
的一个新的独立借用
如果我们在这里没有使用显式生命周期注释,Rust 会使用其生命周期省略规则来得出以下函数签名...
//fn f1<'a>(&'a mut self) -> Option<Chars<'a>>
如您所见,这将引用 &mut self
参数的生命周期绑定到从此函数 return 编辑的 Chars
对象的生命周期(此 Chars
object 不必与 self.chars
相同)这是荒谬的,因为 returned Chars
将比 &mut self
引用更有效。因此我们需要将两个生命周期分开如下...
fn f1<'b>(&'b mut self) -> Option<Chars<'a>> {
self.chars.next();
记住 &mut self
是对 self
的借用,&mut self
引用的任何内容也是借用。因此我们不能在这里 return Some(self.chars)
。 self.chars
不是我们要给的(错误:无法移出借来的内容。)。
我们需要创建一个 self.chars
的克隆,以便它可以分发。
Some(self.chars.clone())
注意这里 returned Chars
与结构 A 具有相同的生命周期。
现在这里f3
没有变化,没有编译错误!
fn f3<'b>(&'b mut self) {
if let Some(x) = self.f1() { //This is ok now
} else {
self.f2() //This is also ok now
}
}
主要功能只是为了完整性...
fn main() {
let mut a = A { chars:"abc".chars() };
a.f3();
for c in a.chars {
print!("{}", c);
}
}
我更新了代码,使生命周期关系更加清晰。
从 Rust 2018 开始,在 Rust 1.31 中可用,original code will work as-is. This is because Rust 2018 enables non-lexical lifetimes。
为什么以下代码中的调用 self.f2()
会触发借用检查器? else 块不是在不同的范围内吗?这真是个难题!
use std::str::Chars;
struct A;
impl A {
fn f2(&mut self) {}
fn f1(&mut self) -> Option<Chars> {
None
}
fn f3(&mut self) {
if let Some(x) = self.f1() {
} else {
self.f2()
}
}
}
fn main() {
let mut a = A;
}
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:16:13
|
13 | if let Some(x) = self.f1() {
| ---- first mutable borrow occurs here
...
16 | self.f2()
| ^^^^ second mutable borrow occurs here
17 | }
| - first borrow ends here
self 的借用范围不是以 self.f1()
调用开始和结束的吗?一旦来自 f1()
的调用返回,f1()
不再使用 self,因此借用检查器应该不会对第二次借用有任何问题。请注意以下代码也失败了...
// ...
if let Some(x) = self.f1() {
self.f2()
}
// ...
我认为第二次借用在这里应该没问题,因为 f1
和 f3
没有与 f2
.
self
可变引用是非常强有力的保证:只有一个指针指向特定的内存位置。既然你已经借了一个&mut
,你就不能再借了。这将在多线程上下文中引入数据竞争,并在单线程上下文中引入迭代器失效和其他类似问题。
现在,借用是基于词法作用域的,所以第一个借用一直持续到函数结束,句点。最终,我们希望放宽此限制,但这需要一些工作。
这很烦人,但您可以通过引入内部作用域并稍微更改控制流来解决此问题:
fn f3(&mut self) {
{
if let Some(x) = self.f1() {
// ...
return;
}
}
self.f2()
}
正如评论中所指出的,这不需要额外的大括号。这是因为 if
或 if...let
表达式具有隐式范围,并且借用持续该范围:
fn f3(&mut self) {
if let Some(x) = self.f1() {
// ...
return;
}
self.f2()
}
这是 Sandeep Datta 和 mbrubeck 之间的 IRC 聊天记录:
mbrubeck: std:tr::Chars contains a borrowed reference to the string that created it. The full type name is
Chars<'a>
. Sof1(&mut self) -> Option<Chars>
without elision isf1(&'a mut self) -> Option<Chars<'a>>
which means thatself
remains borrowed as long as the return value fromf1
is in scope.Sandeep Datta: Can I use 'b for self and 'a for Chars to avoid this problem?
mbrubeck: Not if you are actually returning an iterator over something from
self
. Though if you can make a function from&self -> Chars
(instead of&mut self -> Chars
) that would fix the issue.
我在这里整理了一个示例来展示范围规则:
struct Foo {
a: i32,
}
impl Drop for Foo {
fn drop(&mut self) {
println!("Foo: {}", self.a);
}
}
fn generate_temporary(a: i32) -> Option<Foo> {
if a != 0 { Some(Foo { a: a }) } else { None }
}
fn main() {
{
println!("-- 0");
if let Some(foo) = generate_temporary(0) {
println!("Some Foo {}", foo.a);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(foo) = generate_temporary(1) {
println!("Some Foo {}", foo.a);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(Foo { a: 1 }) = generate_temporary(1) {
println!("Some Foo {}", 1);
} else {
println!("None");
}
println!("-- 1");
}
{
println!("-- 0");
if let Some(Foo { a: 2 }) = generate_temporary(1) {
println!("Some Foo {}", 1);
} else {
println!("None");
}
println!("-- 1");
}
}
这会打印:
-- 0
None
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
None
Foo: 1
-- 1
简而言之,if
子句中的表达式似乎同时存在于 if
块和 else
块中。
一方面这并不奇怪,因为它确实需要比 if
块寿命更长,但另一方面它确实阻止了有用的模式。
如果您更喜欢直观的解释:
if let pattern = foo() {
if-block
} else {
else-block
}
脱糖成:
{
let x = foo();
match x {
pattern => { if-block }
_ => { else-block }
}
}
虽然您希望它脱糖成:
bool bypass = true;
{
let x = foo();
match x {
pattern => { if-block }
_ => { bypass = false; }
}
}
if not bypass {
else-block
}
你不是第一个被这个绊倒的人,所以尽管改变了一些代码的含义(特别是守卫),这个问题可能会在某个时候得到解决。
这是消除虚假错误的方法。我是Rust新手所以以下解释可能有严重错误。
use std::str::Chars;
struct A<'a> {
chars: Chars<'a>,
}
这里的'a
是一个生命周期参数(就像C++中的模板参数)。在 Rust 中类型可以通过生命周期参数化。
Chars
类型也有一个生命周期参数。这意味着 Chars
类型可能有一个需要生命周期参数的成员元素。生命周期参数只对引用有意义(因为这里的生命周期实际上意味着 "lifetime of a borrow")。
我们知道 Chars
需要保留对创建它的字符串的引用,'a
可能用于表示源字符串的生命周期。
这里我们简单地提供 'a
作为 Chars
的生命周期参数,告诉 Rust 编译器 Chars
的生命周期与结构体 [=26= 的生命周期相同]. IMO "lifetime 'a of type A" 应读作 "lifetime 'a of the references contained in the struct A"。
我认为结构实现可以独立于结构本身进行参数化,因此我们需要使用 impl
关键字重复参数。这里我们将名称 'a 绑定到结构 A 的生命周期。
impl<'a> A<'a> {
在函数 f2
的上下文中引入了名称 'b
。这里用来绑定引用的生命周期&mut self
.
fn f2<'b>(&'b mut self) {}
名称'b
是在函数f1
的上下文中引入的。这个'b
与[=29引入的'b
没有直接关系=] 以上。
这里用来绑定引用的生命周期&mut self
。不用说这个引用也和之前函数中的&mut self
没有任何关系,这是对self
.
如果我们在这里没有使用显式生命周期注释,Rust 会使用其生命周期省略规则来得出以下函数签名...
//fn f1<'a>(&'a mut self) -> Option<Chars<'a>>
如您所见,这将引用 &mut self
参数的生命周期绑定到从此函数 return 编辑的 Chars
对象的生命周期(此 Chars
object 不必与 self.chars
相同)这是荒谬的,因为 returned Chars
将比 &mut self
引用更有效。因此我们需要将两个生命周期分开如下...
fn f1<'b>(&'b mut self) -> Option<Chars<'a>> {
self.chars.next();
记住 &mut self
是对 self
的借用,&mut self
引用的任何内容也是借用。因此我们不能在这里 return Some(self.chars)
。 self.chars
不是我们要给的(错误:无法移出借来的内容。)。
我们需要创建一个 self.chars
的克隆,以便它可以分发。
Some(self.chars.clone())
注意这里 returned Chars
与结构 A 具有相同的生命周期。
现在这里f3
没有变化,没有编译错误!
fn f3<'b>(&'b mut self) {
if let Some(x) = self.f1() { //This is ok now
} else {
self.f2() //This is also ok now
}
}
主要功能只是为了完整性...
fn main() {
let mut a = A { chars:"abc".chars() };
a.f3();
for c in a.chars {
print!("{}", c);
}
}
我更新了代码,使生命周期关系更加清晰。
从 Rust 2018 开始,在 Rust 1.31 中可用,original code will work as-is. This is because Rust 2018 enables non-lexical lifetimes。