约束借用IMPH特征对象的寿命

发布于 2025-01-22 01:48:50 字数 2398 浏览 1 评论 0原文

考虑以下代码:

trait MyTrait<'a, 'b: 'a> {
    fn f(&'b mut self) -> &'a str;
}

struct MyStruct {
    my_string: String,
}

impl<'a, 'b: 'a> MyTrait<'a, 'b> for MyStruct {
    fn f(&'b mut self) -> &'a str {
        &self.my_string
    }
}

fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
    bar(s);
    bar(s); // ERROR
}

fn foo2<'a>(s: &mut MyStruct) {
    bar(s);
    bar(s);
}

fn bar<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
    let x = s.f();
}

特质myTrait定义了返回str参考的函数。它的寿命可以解释为“返回str参考的对象至少将与该参考有关。”

bar是一个函数,它接受对实现mytrait和调用f的结构的可变引用。功能foofoo2与唯一的区别是foo2接受特定类型时foo接受任何实现mytrait的具体类型。

由于foo2编译,因此我期望foo也会编译,但事实并非如此。这是错误消息:

error[E0499]: cannot borrow `*s` as mutable more than once at a time
  --> src\bin\main2.rs:17:6
   |
15 | fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
   |            -- lifetime `'b` defined here
16 |     bar(s);
   |     ------
   |     |   |
   |     |   first mutable borrow occurs here
   |     argument requires that `*s` is borrowed for `'b`
17 |     bar(s); // ERROR
   |         ^ second mutable borrow occurs here

据我了解,这是正在发生的事情:myTrait迫使实现其生存的对象'b。第一个调用bar借款s lifetime 'b,这意味着借用不会在bar的末尾下降像常规借用一样,但仅在'b的末尾下降。 foobar具有相同的功能签名,因此指定的寿命将相同。这意味着,当bar mutyable借入s时,此借款只会在'B的结尾处出现,并在<的结尾处出现。代码> foo。第二个bar调用将遵循相同的逻辑,但是由于已经存在可变的借用,因此编译器不允许创建另一个。

最初,我认为一种解决方案是将bar的签名重写为:

fn bar<'a, 'b: 'a, 'c: 'b>(s: &'b mut impl MyTrait<'a, 'c>)

希望编译器将其解释为:'b仅在功能的持续时间内生存,'a,甚至受到进一步的约束,对象本身*s生命周期'C,它超过了函数” 。 但是,这导致了错误:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements

是否可以将bar中的借款限制为仅在bar的持续时间内实时?

Consider the following code:

trait MyTrait<'a, 'b: 'a> {
    fn f(&'b mut self) -> &'a str;
}

struct MyStruct {
    my_string: String,
}

impl<'a, 'b: 'a> MyTrait<'a, 'b> for MyStruct {
    fn f(&'b mut self) -> &'a str {
        &self.my_string
    }
}

fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
    bar(s);
    bar(s); // ERROR
}

fn foo2<'a>(s: &mut MyStruct) {
    bar(s);
    bar(s);
}

fn bar<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
    let x = s.f();
}

The trait MyTrait defines a function that returns a str reference. Its lifetime can be interpreted as "the object that returned the str reference will live at least as long as that reference.".

bar is a function that accepts a mutable reference to a struct that implements MyTrait and calls f. The functions foo and foo2 are identical with the only difference being that foo2 accepts a specific type while foo accepts any concrete type that implements MyTrait.

Since foo2 compiles, I expected foo to compile as well but it didn't. Here is the error message:

error[E0499]: cannot borrow `*s` as mutable more than once at a time
  --> src\bin\main2.rs:17:6
   |
15 | fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
   |            -- lifetime `'b` defined here
16 |     bar(s);
   |     ------
   |     |   |
   |     |   first mutable borrow occurs here
   |     argument requires that `*s` is borrowed for `'b`
17 |     bar(s); // ERROR
   |         ^ second mutable borrow occurs here

From what I understand, here is what is going on: MyTrait forces the object that implements it live for the lifetime 'b. The first call to bar borrows s for the lifetime 'b, meaning that the borrow won't drop at the end of bar like a regular borrow, but will drop only at the end of 'b. foo and bar have the same function signature and therefore the lifetimes specified will be identical. This means that when bar mutably borrows s, this borrow will only go out of scope at then end of 'b - at the end of foo. The second bar call will follow the same logic, however since there already exists a mutable borrow, the compiler won't allow another one to be created.

Initially I thought a solution was to rewrite the signature of bar as:

fn bar<'a, 'b: 'a, 'c: 'b>(s: &'b mut impl MyTrait<'a, 'c>)

Hoping the compiler would interpret it as: "'b lives only for the duration of the function, 'a, is even further constrained, and the object itself *s lives for the lifetime 'c which outlives the function".
However it resulted in the error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements

Is it possible to constrain the borrow in bar to only live for the duration of bar?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

贱人配狗天长地久 2025-01-29 01:48:50

我不确定您是如何到达发布的代码的,但是我发现最好的经验法则是没有明确的终身参数,并且仅在编译器说需要它们时才添加它们。

在您的情况下,编译器不需要终身参数,很乐意将它们全部弄清楚:

trait MyTrait {
    fn f(&mut self) -> &str;
}

struct MyStruct {
    my_string: String,
}

impl MyTrait for MyStruct {
    fn f(&mut self) -> &str {
        &self.my_string
    }
}

fn foo(s: &mut impl MyTrait) {
    bar(s);
    bar(s);
}

fn foo2(s: &mut MyStruct) {
    bar(s);
    bar(s);
}

fn bar(s: &mut impl MyTrait) {
    let x = s.f();
}

特别是,对于获取一个参考并返回参考的任何功能,编译器可以算出返回的参考必须

trait MyTrait {
    fn f<'a>(&'a mut self) -> &'a str;
}

impl MyTrait for MyStruct {
    fn f<'a>(&'a mut self) -> &'a str {
        &self.my_string
    }
}

请注意(1),寿命在上是在特征上不在上。这是添加寿命参数时,这是另一个好的经验法则,始终将它们放在最特定的范围中。 (2),不需要两个参数。


编辑:

就理解为什么不喜欢您的版本而言,我认为这基本上是因为生命周期是在性状上定义的,而不是在功能上定义的。 mytrait尤其是类型的功能,而不是任何特定的代码路径的功能。对于所有编译器都知道,对于通用的mytrait&lt;'a,''b&gt;这些生命可能与代码中其他地方的其他内容联系在一起,因此无法知道'b无法将您的调用与foo> >“ B s”一定是通用的。

但是在foo2中,编译器可以看到myStruct的所有寿命,它可以将这些寿命崩溃到它拥有的具体案例中,并且知道它只需要借用<代码> s 在bar的持续时间内。

I'm not sure how you got to the code you posted, but I find the best rule of thumb is to start out with no explicit lifetime parameters, and only add them when the compiler says it needs them.

In your case, the compiler needs no lifetime parameters, it's perfectly happy to figure them all out itself:

trait MyTrait {
    fn f(&mut self) -> &str;
}

struct MyStruct {
    my_string: String,
}

impl MyTrait for MyStruct {
    fn f(&mut self) -> &str {
        &self.my_string
    }
}

fn foo(s: &mut impl MyTrait) {
    bar(s);
    bar(s);
}

fn foo2(s: &mut MyStruct) {
    bar(s);
    bar(s);
}

fn bar(s: &mut impl MyTrait) {
    let x = s.f();
}

In particular, for any function taking one reference and returning a reference, the compiler can work out that the returned reference must be from the argument, so no explicit bounds are needed. What it's actually doing (AFAIK) is basically inserting these lifetimes:

trait MyTrait {
    fn f<'a>(&'a mut self) -> &'a str;
}

impl MyTrait for MyStruct {
    fn f<'a>(&'a mut self) -> &'a str {
        &self.my_string
    }
}

Note how (1), the lifetime is on the function not on the trait. This is another good rule of thumb when adding lifetime parameters, always put them in the most specific scope possible. Also (2), there's no need for two parameters.


Edit:

As far as understanding why it didn't like your version specifically, I think it's basically because the lifetime is defined on the trait rather than on the function. The lifetime 'b on MyTrait in particular is a feature of the type, not of any particular code path. For all the compiler knows, for a generic MyTrait<'a, 'b> those lifetimes could be tied to something else entirely elsewhere in the code, it has no way of knowing that 'b cannot outlive your call to foo because it has to be generic over all possible 'bs.

But in foo2, the compiler can see that all of the lifetimes of MyStruct, it can collapse those lifetimes to the concrete case it has and knows that it only needs to borrow s for the duration of bar.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文