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

dsp*_*pyz 3 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(())
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为什么这不起作用?


回应Paolo Falabella的回答.

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

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

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

由此可见,借款超出了范围'inner.

是否有任何问题而是使用以下重写进行借用检查?

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

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

Pao*_*lla 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在没有返回时才能达到.但事实并非如此.我甚至不确定教它那种分析是多么困难,因为它比我理解借用检查器的理由要复杂得多.

今天,编译器看到了一个引用,并且基本上试图回答一个问题("这个问题有多长?").当它看到你的返回值是lk,它会lk从fn的签名('a我们在上面给出的显式名称)中为返回值指定它所期望的生命周期,并将其称为一天.

简而言之:

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

暂时我建议只返回克隆而不是引用,如果可能的话.我假设返回a 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(())
    }
}
Run Code Online (Sandbox Code Playgroud)