为什么早退不完成优秀借款?

时间:2016-01-18 00:26:20

标签: return rust borrow-checker

我尝试编写一个函数,只有当元素大于向量中已有的最后一个元素时,才将元素推送到有序向量的末尾,否则返回带有ref的错误给最大元素。据我所知,这似乎并没有违反任何借款规则,但借款检查员并不喜欢它。我不明白为什么。

struct MyArray<K, V>(Vec<(K, V)>);

impl<K: Ord, V> MyArray<K, V> {
    pub fn insert_largest(&mut self, k: K, v: V) -> Result<(), &K> {
        {
            match self.0.iter().next_back() {
                None => (),
                Some(&(ref lk, _)) => {
                    if lk > &k {
                        return Err(lk);
                    }
                }
            };
        }
        self.0.push((k, v));
        Ok(())
    }
}

error[E0502]: cannot borrow `self.0` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:9
   |
6  |             match self.0.iter().next_back() {
   |                   ------ immutable borrow occurs here
...
15 |         self.0.push((k, v));
   |         ^^^^^^ mutable borrow occurs here
16 |         Ok(())
17 |     }
   |     - immutable borrow ends here

为什么这不起作用?

回应Paolo Falabella's answer

我们可以将带有return语句的任何函数转换为没有return语句的函数,如下所示:

fn my_func() -> &MyType {
    'inner: {
        // Do some stuff
        return &x;
    }
    // And some more stuff
}

fn my_func() -> &MyType {
    let res;
    'outer: {
        'inner: {
            // Do some stuff
            res = &x;
            break 'outer;
        }
        // And some more stuff
    }
    res
}

由此可见,借款的范围超过了'inner

是否存在使用以下重写进行借用检查的问题?

fn my_func() -> &MyType {
    'outer: {
        'inner: {
            // Do some stuff
            break 'outer;
        }
        // And some more stuff
    }
    panic!()
}

考虑到返回声明可以排除事后发生的任何事情,否则可能会违反借用规则。

2 个答案:

答案 0 :(得分:7)

如果我们明确命名生命时间,insert_largest的签名将变为fn insert_largest<'a>(&'a mut self, k: K, v: V) -> Result<(), &'a K>。因此,当您创建返回类型&K时,其生命周期将与&mut self相同。

事实上,你正在从lk内部取得self。 编译器看到对lk的引用转义了匹配的范围(因为它被分配给函数的返回值,因此它必须比函数本身更长)并且它不能让借用比赛结束时结束。

我认为你说编译器应该更聪明,并且意识到只有在self.0.push没有返回时才能达到lk。但事实并非如此。而且我甚至不确定教它那种分析是多么困难,因为它比我理解借用检查器的理由要复杂得多。

今天,编译器看到了一个引用,并且基本上试图回答一个问题(&#34;它存活了多长时间?&#34;)。当它看到您的返回值为lk时,它会为fn的签名(lk的返回值指定'a生命周期,并使用我们给出的明确名称以上)并称之为一天。

简而言之:

  • 是否应该提前退货结束对自我的可变借款?否。如上所述,借款应延伸到函数之外并遵循其返回值
  • 是从函数的早期返回到结尾的代码中的借用检查器有点过于严格吗?是的,我是这么认为的。早期返回之后和函数结束之前的部分只有在函数没有提前返回时才可以访问,所以我认为你有一个观点,即在特定代码区域借用时借用检查可能不那么严格
  • 我认为更改编译器以启用该模式是否可行/可取?我不知道。借用检查器是Rust编译器中最复杂的部分之一,我没有资格为您提供答案。这似乎与non-lexical borrow scopes的讨论有关(甚至可能是其中的一部分),所以我鼓励您研究它,如果您对此主题感兴趣,可能会有所贡献。

暂时我建议只是返回一个克隆而不是一个参考,如果可能的话。我假设返回Err不是典型的情况,因此性能不应该特别担心,但我不确定K:Clone绑定如何与您所使用的类型一起使用使用

impl <K, V> MyArray<K, V> where K:Clone + Ord { // 1. now K is also Clone
    pub fn insert_largest(&mut self, k: K, v: V) -> 
                                    Result<(), K> { // 2. returning K (not &K)
        match self.0.iter().next_back() {
            None => (),
            Some(&(ref lk, _)) => {
                if lk > &k {
                    return Err(lk.clone()); // 3. returning a clone
                }
            }
        };
        self.0.push((k, v));
        Ok(())
    }
}

答案 1 :(得分:1)

  

为什么早归还不能完成未偿还的借款?

因为借阅检查器的当前实现过于保守。

启用non-lexical lifetimes后,您的代码将按原样工作,但仅适用于实验性的“ Polonius”实现。 Polonius使有条件的借入跟踪成为可能。

我还简化了您的代码:

#![feature(nll)]

struct MyArray<K, V>(Vec<(K, V)>);

impl<K: Ord, V> MyArray<K, V> {
    pub fn insert_largest(&mut self, k: K, v: V) -> Result<(), &K> {
        if let Some((lk, _)) = self.0.iter().next_back() {
            if lk > &k {
                return Err(lk);
            }
        }

        self.0.push((k, v));
        Ok(())
    }
}