Rust 为什么借还保留在if let的else块中?

Rust 为什么借还保留在if let的else块中?,rust,Rust,为什么在下面的代码中调用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) = s

为什么在下面的代码中调用
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;
}
借用for self的范围不是以
self.f1()
调用开始和结束吗?一旦来自
f1()
的调用返回
f1()
不再使用self,则借阅检查器应该不会对第二次借阅有任何问题。注意下面的代码也失败了

// ...
if let Some(x) = self.f1() {
    self.f2()
}
// ...

我认为第二次借用应该可以,因为
f1
f3
没有与
f2
同时使用
self
,可变引用是一个非常有力的保证:只有一个指向特定内存位置的指针。既然你已经有了一个
&mut
借用,你就不能再有第二个了。这将在多线程上下文中引入数据竞争,在单线程上下文中引入迭代器失效和其他类似问题


现在,借词是基于词法范围的,所以第一次借词一直持续到函数period结束。最终,我们希望放宽此限制,但这需要一些工作。

这很烦人,但您可以通过引入内部作用域并稍微更改控制流来解决此问题:

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包含对创建它的字符串的借用引用。完整的类型名是
Chars Option我在这里放了一个示例来展示范围规则:

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");
    }
}
简言之,
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
}
虽然您更希望它可以分解为:

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

您并不是第一个被这一错误绊倒的人,因此,尽管某些代码(特别是防护)的含义发生了变化,但在某些情况下可能会解决这一问题。

以下是如何消除这些错误。我是新手,所以下面的解释可能会有严重的错误

use std::str::Chars;

struct A<'a> {
    chars: Chars<'a>,
}
名称
'b
是在函数
f2
的上下文中引入的。在这里,它用于绑定引用的生存期
&mut self

fn f2<'b>(&'b mut self) {}
如您所见,这将引用
&mut self
参数的生存期绑定到此函数返回的
Chars
对象的生存期(此
Chars
对象不必与
self.Chars
相同)这是荒谬的,因为返回的
字符将比
&mut self
引用更有效。因此,我们需要将这两个生命周期分开,如下所示

fn f1<'b>(&'b mut self) -> Option<Chars<'a>> {
    self.chars.next();
注意这里返回的
Chars
与结构A具有相同的生存期

impl<'a> A<'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
    }
}

我已经更新了代码,使生命周期关系更加清晰。

从Rust 2018开始,可在Rust 1.31中找到。这是因为Rust 2018启用。

即使您移除了if let
表达式中
周围的大括号,此选项仍然有效。答案被接受,因为除了解释之外,您还提供了一个解决方法。但是我认为后续问题尚未完全回答。“只要f1的返回值在范围内,self就保持借用状态。”听起来像是借用检查器现在工作方式的产物。这里有几个不同的生命周期相互混淆,
self.f1
borrow与
self.f3
borrow没有相同的生命周期,但它们是由相同的生命周期
'a
表示的。我想,仅仅对同一数据的两个可变引用的可用性不会在短时间内引入数据竞争本身:)至少,您需要两个线程才能获得数据竞争。这可能不是数据竞争,但仍然存在问题,如迭代器失效是的,当然。我只是特别反对“数据竞赛”这个术语。
fn main() {
    let mut a = A { chars:"abc".chars() };

    a.f3();

    for c in a.chars {
        print!("{}", c);
    }
}