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

时间:2015-05-14 17:28:55

标签: 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;
}

Playground

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.f1()来电开始和结束?一旦来自f1()的呼叫返回f1()不再使用自己,因此借用检查器不应该对第二次借用有任何问题。请注意,以下代码也失败了......

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

Playground

我认为第二次借用应该没有问题,因为f1f3未与self同时使用f2

5 个答案:

答案 0 :(得分: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");
    }
}

打印:

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

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

答案 1 :(得分:4)

这很烦人,但你可以通过引入内部范围并稍微改变控制流程来解决这个问题:

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

正如评论中所指出的,这没有额外的支撑。这是因为ifif...let表达式具有隐式范围,并且借用持续此范围:

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

    self.f2()
}

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

  

mbrubeck: std:tr :: Chars包含对创建它的字符串的借用引用。完整类型名称为Chars<'a>。因此f1(&mut self) -> Option<Chars>没有省略f1(&'a mut self) -> Option<Chars<'a>>,这意味着只要self仍然借用f1   self的返回值在范围内。

     

Sandeep Datta:我可以使用&#39; b代替Chars来避免这个问题吗?

     

mbrubeck:如果您实际上正在从&self -> Chars返回某个迭代器,那就不是了。但是,如果您可以通过&mut self -> Chars(而非AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil]; //local file url with custom scheme AVAssetResourceLoader *loader = [asset resourceLoader]; [loader setDelegate:self queue:dispatch_get_main_queue()]; self.playerItem = [AVPlayerItem playerItemWithAsset:asset]; self.player = [AVPlayer playerWithPlayerItem:self.playerItem]; [self.playerView setPlayer:self.player]; )创建可以解决问题的功能。

答案 2 :(得分:3)

可变引用是非常强保证:只有一个指向特定内存位置的指针。由于您已经有一次&mut借款,因此您也无法获得第二次借款。这将在多线程上下文中引入数据争用,并在单线程上下文中迭代失效和其他类似问题。

现在,借用是基于词汇范围,因此第一次借用持续到功能结束时段。最终,我们希望放宽这个限制,但这需要一些工作。

答案 3 :(得分:3)

以下是如何摆脱虚假错误的方法。我是Rust的新手,所以在下面的解释中可能会出现严重错误。

use std::str::Chars;

struct A<'a> {
    chars: Chars<'a>,
}

'a这里是一个生命周期参数(就像C ++中的模板参数一样)。类型可以通过Rust中的生命周期进行参数化。

Chars类型也需要一个生命周期参数。这意味着Chars类型可能有一个需要生命周期参数的成员元素。生命周期参数仅对引用有意义(因为这里的生命实际上意味着&#34;借用的生命周期&#34;)。

我们知道Chars需要保留对创建它的字符串的引用,'a可能会用来表示源字符串的生命周期。

这里我们只提供'a作为Chars的生命周期参数,告诉Rust编译器Chars的生命周期与结构A的生命周期相同。 IMO&#34; a&#39; a型A&#34;应该被理解为&#34;结构A&#34;中包含的引用的生命周期。

我认为struct实现可以独立于struct本身进行参数化,因此我们需要使用impl关键字重复参数。在这里,我们将名称&#39; a绑定到结构A的生命周期。

impl<'a> A<'a> {

名称'b是在函数f2的上下文中引入的。在这里,它用于绑定引用&mut self的生命周期。

fn f2<'b>(&'b mut self) {}

名称'b是在函数f1的上下文中引入的。'b'b引入的f2没有直接关系以上。

此处用于绑定引用&mut self的生命周期。不用说这个引用也与前一个函数中的&mut self没有任何关系,这是对self的新独立借用。

如果我们没有使用显式生命周期注释,Rust会使用它的生命周期省略规则来得到以下函数签名...

//fn f1<'a>(&'a mut self) -> Option<Chars<'a>>

正如您所看到的,这会将引用&mut self参数的生命周期绑定到从此函数返回的Chars对象的生存期(此Chars对象不必与self.chars)这是荒谬的,因为返回的Chars将比&mut self引用更长。因此,我们需要将两个生命周期分开如下......

fn f1<'b>(&'b mut self) -> Option<Chars<'a>> {
    self.chars.next();

请记住&mut self借用self&mut self引用的任何内容也是借用。因此,我们不能在这里返回Some(self.chars)self.chars不是我们的(错误:无法摆脱借来的内容。)。

我们需要创建一个self.chars的克隆,以便可以发出它。

Some(self.chars.clone())

请注意,返回的Chars与struct 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);
    }
}

我更新了代码,使终身关系更加清晰。

答案 4 :(得分:1)

从Rust 2018开始,Rust 1.31中的original code will work as-is可用。这是因为Rust 2018启用了non-lexical lifetimes