在Rust函数中出现奇怪的生命周期,第二个可变借用发生。

huangapple go评论123阅读模式
英文:

strange lifetime in rust functions, second mutable borrow occurs

问题

第一个代码示例中有一个生命周期标记<'a>,但无法通过编译。第二个代码示例有两个生命周期标记<'a, 'b>,但可以通过编译。这是为什么呢?

第一个代码示例的问题在于生命周期标记的使用方式。具体来说,deserialize1deserialize2 函数都接受一个类型为 &amp;&#39;a mut I 的参数,表示它们需要可变引用来借用 I。然后,deserialize3 函数接受一个 &amp;&#39;a mut I 参数,并尝试同时调用 deserialize1deserialize2,这将导致同时对 iter 进行两次可变借用,这是不允许的,因此编译器报错。

第二个代码示例修复了这个问题。它使用了两个生命周期标记<'a, 'b>,并相应地更新了函数签名,使得 deserialize1deserialize2 可以接受不同的生命周期标记。然后,deserialize3 函数也采用了相同的生命周期标记,因此它可以同时调用 deserialize1deserialize2,因为它们现在可以接受不同的引用,不会同时对 iter 进行可变借用。

总之,生命周期标记在 Rust 中用于确保引用的有效性和避免悬垂引用等问题。在编写涉及多个引用的函数时,必须正确使用生命周期标记以满足 Rust 的借用规则。第二个代码示例中的生命周期标记允许编译器正确理解引用的生命周期,从而通过编译。

英文:

I have two pieces of rust codes about lifetime. The first one has only one lifetime mark <'a> which can't pass compilation. The second one has two lifetime marks <'a, 'b> which can pass compilation. What's the reason of this?

The first one:

fn deserialize1&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(_iter: &amp;&#39;a mut I) {
}
fn deserialize2&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(_iter: &amp;&#39;a mut I) {
}
fn deserialize3&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(iter: &amp;&#39;a mut I) {
    deserialize1(iter);
    deserialize2(iter);
}

fn main() {
    let v : Vec&lt;u8&gt; = vec![1, 2, 3];
    let mut iter = v.iter();
    deserialize3(&amp;mut iter);
}

The second one:

fn deserialize1&lt;&#39;a, &#39;b, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(_iter: &amp;&#39;b mut I) {
}
fn deserialize2&lt;&#39;a, &#39;b, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(_iter: &amp;&#39;b mut I) {
}
fn deserialize3&lt;&#39;a, &#39;b, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(iter: &amp;&#39;b mut I) {
    deserialize1(iter);
    deserialize2(iter);
}

fn main() {
    let v : Vec&lt;u8&gt; = vec![1, 2, 3];
    let mut iter = v.iter();
    deserialize3(&amp;mut iter);
}

The first one compile failed with:

error[E0499]: cannot borrow `*iter` as mutable more than once at a time
  --&gt; src/main.rs:33:18
   |
31 | fn deserialize3&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(iter: &amp;&#39;a mut I) {
   |                 -- lifetime `&#39;a` defined here
32 |     deserialize1(iter);
   |     ------------------
   |     |            |
   |     |            first mutable borrow occurs here
   |     argument requires that `*iter` is borrowed for `&#39;a`
33 |     deserialize2(iter);
   |                  ^^^^ second mutable borrow occurs here

I can't understand above compilation info. Why lifetime can affect borrow rules?

答案1

得分: 2

当你在这里使用一个生命周期,就像你在这里所做的一样,你过度限制了你的引用。为什么迭代器的生命周期必须与它产生的引用的生命周期匹配?在这种情况下,迭代器的'a更受限制,这意味着编译器根本不能调整生命周期。

好的,在这里使用单一生命周期时会有很多不灵活性,但这为什么是个问题呢?由于'a是不变的,并且你将相同的东西传递给了deserialize1deserialize2,它们的定义方式相同,这意味着在每个函数中iter: &'a mut I都意味着完全相同的东西。两个函数都试图独占借用iter,生命周期都是'a,而这显然是不被Rust借用检查器允许的。

当你引入iter来使用不同的生命周期'b时,你允许编译器为每个可变借用选择不同的生命周期,从而不会冲突。

为什么生命周期可以影响借用规则?

生命周期注释存在的唯一目的是通知借用检查器引用的使用方式。显然,如果你的生命周期注释与你的使用方式不符,借用检查器会捕获到并报错。


最后注意一下,你不需要明确提到'b。根据生命周期省略规则,你可以将iter不加注释,这将与你的第二个变体完全相同:

fn deserialize1<'a, I: Iterator<Item = &'a u8>>(_iter: &mut I) {}
fn deserialize2<'a, I: Iterator<Item = &'a u8>>(_iter: &mut I) {}
fn deserialize3<'a, I: Iterator<Item = &'a u8>>(iter: &mut I) {
    deserialize1(iter);
    deserialize2(iter);
}
英文:

When you use one lifetime as you've done here, you are over-constraining your references. Why does the iterator's lifetime have to match the lifetime of the references it yields? Its even more constrained since the &#39;a used for the iterator Item is invariant in this case meaning the compiler can't adjust the lifetime at all.

Ok, there's a lot of inflexibility when using a single lifetime here, but why is that a problem? Since &#39;a is invariant, and you're passing the same thing to deserialize1 and deserialize2 which are defined the same way, that means that iter: &amp;&#39;a mut I means the exact same thing in each function. Both functions are trying to exclusively borrow iter for the same exact lifetime &#39;a and that is plainly not allowed by the Rust borrow checker.

When you introduce iter to use a different lifetime &#39;b, you allow the compiler to choose a different lifetime for each mutable borrow and thus won't conflict.

> Why lifetime can affect borrow rules?

Lifetime annotations exist solely to inform the borrow checker of how references are used. Obviously if you annotate your lifetimes incorrectly compared to your usage, the borrow checker would catch that and present an error.


As a final note, you don't need to mention &#39;b explicitly. You can leave iter un-annotated and will be identical to your second variant due to lifetime elision rules:

fn deserialize1&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(_iter: &amp;mut I) {}
fn deserialize2&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(_iter: &amp;mut I) {}
fn deserialize3&lt;&#39;a, I: Iterator&lt;Item = &amp;&#39;a u8&gt;&gt;(iter: &amp;mut I) {
    deserialize1(iter);
    deserialize2(iter);
}

huangapple
  • 本文由 发表于 2023年8月10日 10:08:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76872240.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定