代码之家  ›  专栏  ›  技术社区  ›  Victor Basso

不能在if let语句[duplicate]的else块中可变地借用

  •  1
  • Victor Basso  · 技术社区  · 6 年前

    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

    我想第二次借钱应该没问题 f1 f3 self f2 .

    0 回复  |  直到 7 年前
        1
  •  5
  •   fghj    7 年前

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

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

    B支管: Chars<'a> . 所以 f1(&mut self) -> Option<Chars> 没有省略 f1(&'a mut self) -> Option<Chars<'a>> 也就是说 self 只要 返回值来自 f1 在范围内。

    Sandeep数据: 我可以用'b代表self和'a代表Chars来避免这个问题吗?

    如果您实际上是在从 自己 . 如果你能从 &self -> Chars &mut self -> Chars )这样就能解决问题了。

        2
  •  11
  •   Shepmaster Tim Diekmann    7 年前

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

    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 子句通过 如果 else 阻止。

    如果

    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
    }
    

        3
  •  3
  •   Shepmaster Tim Diekmann    7 年前

    可变引用是 非常 强保证:只有一个指针指向特定的内存位置。既然你已经有过了 &mut 借,你不能也有一秒。这将在多线程上下文中引入数据竞争,并在单线程上下文中引入迭代器失效和其他类似问题。

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

        4
  •  3
  •   Shepmaster Tim Diekmann    7 年前

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

    use std::str::Chars;
    
    struct A<'a> {
        chars: Chars<'a>,
    }
    

    这个 'a

    Chars 类型还接受生存期参数。这意味着 类型可能具有需要生存期参数的成员元素。Lifetime参数只对引用有意义(因为Lifetime在这里实际上意味着“借用的生命周期”)。

    “a” 可能用于表示源字符串的生存期。

    这里我们只供应 “a” 告诉Rust编译器 A . IMO“a类型的lifetime‘a”应该读作“a结构中包含的引用的‘lifetime’a”。

    我认为结构实现可以独立于结构本身进行参数化,因此我们需要使用 impl

    impl<'a> A<'a> {
    

    'b f2 . 这里它被用来绑定引用的生存期 &mut self

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

    名字 “b” f1 .这个 “b” “b” 二楼 以上。

    这里它被用来绑定引用的生存期 & . 不用说,这个提法也和 &多重自我 self

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

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

    &多重自我 参数的生存期 对象不必与 self.chars 会比 & 参考。因此,我们需要将这两个生命周期分开如下。。。

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

    记得 &多重自我 是借来的 以及任何提到的 &多重自我 Some(self.chars) 在这里。 自身字符

    我们需要创建一个 自身字符 这样就可以分发出去了。

    Some(self.chars.clone())
    

    与结构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);
        }
    }
    

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

        5
  •  2
  •   Shepmaster Tim Diekmann    6 年前

    从Rust 2018起,在Rust 1.31中提供 original code will work as-is . 这是因为《铁锈2018》使 non-lexical lifetimes .