为什么借入仍然存在于if的else块中?

Aut*_*act 10 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) = self.f1() {

        } else {
            self.f2()
        }
    }
}

fn main() {
    let mut a = A;
}
Run Code Online (Sandbox Code Playgroud)

操场

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
Run Code Online (Sandbox Code Playgroud)

自我借款的范围不是以self.f1()电话开始和结束的吗?一旦来自f1()已返回的呼叫f1()不再使用自己,因此借用检查器不应该对第二次借用有任何问题.请注意以下代码也失败了......

// ...
if let Some(x) = self.f1() {
    self.f2()
}
// ...
Run Code Online (Sandbox Code Playgroud)

操场

我认为次贷应该是在这里很好,因为f1f3没有使用self在同一时间f2.

Mat*_* M. 9

我举了一个例子来展示这里的范围规则:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这打印:

-- 0
None
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
Some Foo 1
Foo: 1
-- 1
-- 0
None
Foo: 1
-- 1
Run Code Online (Sandbox Code Playgroud)

简而言之,似乎if子句中的表达式同时存在于if块和else块中.

一方面它并不奇怪,因为确实需要比if块更长寿,但另一方面它确实阻止了有用的模式.

如果您更喜欢视觉说明:

if let pattern = foo() {
    if-block
} else {
    else-block
}
Run Code Online (Sandbox Code Playgroud)

desugars into:

{
    let x = foo();
    match x {
    pattern => { if-block }
    _ => { else-block }
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然你更喜欢它去了:

bool bypass = true;
{
    let x = foo();
    match x {
    pattern => { if-block }
    _ => { bypass = false; }
    }
}
if not bypass {
    else-block
}
Run Code Online (Sandbox Code Playgroud)

你并不是第一个因此被绊倒的人,所以尽管改变了某些代码的含义(特别是警卫),这可能会在某个时候解决.


小智 6

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

fn f3(&mut self) {
    {
        if let Some(x) = self.f1() {
            // ...
            return;
        }
    }
    self.f2()
}
Run Code Online (Sandbox Code Playgroud)

正如评论中所指出的,这不需要额外的大括号。这是因为iforif...let表达式具有隐式作用域,并且借用持续用于此作用域:

fn f3(&mut self) {
    if let Some(x) = self.f1() {
        // ...
        return;
    }

    self.f2()
}
Run Code Online (Sandbox Code Playgroud)

这是 Sandeep Datta 和 mbrubeck 之间的 IRC 聊天记录:

mbrubeck: std:tr::Chars 包含对创建它的字符串的借用引用。完整的类型名称是Chars<'a>. 所以f1(&mut self) -> Option<Chars>没有省略 isf1(&'a mut self) -> Option<Chars<'a>>这意味着self只要返回值f1在范围内就保持借用。

Sandeep Datta:我可以将 'b 用于 self 并将 'a 用于 Chars 来避免这个问题吗?

mbrubeck:如果您实际上是从self. 但是,如果您可以从&self -> Chars(而不是&mut self -> Chars)创建一个函数来解决问题。