寿命扣除和在通用函数中借用(用于通用测试)

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

Lifetime deduction and borrowing in generic functions (required for generic-tests)

问题

以下是翻译好的代码部分:

假设有一个简单的特性,允许附加对象引用。这里我使用`String`来使代码稍微简化。

trait Attachable<'a> {
fn new() -> Self;
fn attach(&mut self, value: &'a String);
}


一个简单的实现可能如下所示:
```rust
struct SomeAttachable<'a> {
    id: Option<&'a String>,
}

impl<'a> Attachable<'a> for SomeAttachable<'a> {
    fn new() -> Self {
        Self { id: None }
    }
    fn attach(&mut self, value: &'a String) {
        self.id = Some(value);
    }
}

并且可以直接使用它。

let mut object = SomeAttachable::new();
let value = "hello".to_string();
object.attach(&value);

但是当将它放入一个泛型函数中,仅提供可附加类型时,会出现问题。

fn do_stuff<'a, T: Attachable<'a>>() {
    let mut object = T::new();
    let value: String = "hello".to_string();
    object.attach(&value); // 这里会出现问题,因为value的生命周期不够长
}

我认为生命周期在调用函数do_stuff时被检测,然后value有一个“错误”的生命周期要求。如何在do_stuff的实现中纠正生命周期问题。

将函数签名调整为:

fn do_stuff_with_argument<'a, T: Attachable<'a>>(value: &'a String) {
    let mut bla = T::new();
    bla.attach(value);
}

将解决该问题,因为现在生命周期再次被正确检测,因为它是输入引用参数的一部分。但这对我来说不是一个合适的解决方案。do_stuff函数应在函数内部处理所有逻辑,而不需要任何函数参数。只允许泛型参数,如生命周期和类型。

我认为我可能需要使用Higher-Rank Trait Bounds并像这样实现do_stuff

fn do_stuff<T>()
where
    T: for<'a> Attachable<'a>,
{
    let mut object = T::new();
    let value: String = "hello".to_string();
    object.attach(&value);
}

但这会导致Rust抱怨Attachable的实现对于SomeAttachable来说不够泛型。

为了提供一些上下文:我需要这个用于基于generic-test crate的单元测试,其中验证了特性的行为。

英文:

Assume a simple trait which allows an object reference to be attached. I used String here to make the code a little bit simpler.

trait Attachable&lt;&#39;a&gt; {
    fn new() -&gt; Self;
    fn attach(&amp;mut self, value: &amp;&#39;a String);
}

A simplistic implementation could look like:

struct SomeAttachable&lt;&#39;a&gt; {
    id: Option&lt;&amp;&#39;a String&gt;,
}

impl&lt;&#39;a&gt; Attachable&lt;&#39;a&gt; for SomeAttachable&lt;&#39;a&gt; {
    fn new() -&gt; Self {
        Self { id: None }
    }
    fn attach(&amp;mut self, value: &amp;&#39;a String) {
        self.id = Some(value)
    }
}

and using it works out of the box.

let mut object = SomeAttachable::new();
let value = &quot;hello&quot;.to_string();
object.attach(&amp;value);

But when this is put into a generic function where only the attachable type is provided it breaks.

fn do_stuff&lt;&#39;a, T: Attachable&lt;&#39;a&gt;&gt;() {
    let mut object = T::new();
    let value: String = &quot;hello&quot;.to_string();
    object.attach(&amp;value); // breaks here since value does not live long enough
}

I assume that the lifetime is detected when the function do_stuff is called and then value has a "wrong" lifetime requirement. How can I correct the lifetime issue in the implementation do_stuff.

Adjusting the function signature to:

fn do_stuff_with_argument&lt;&#39;a, T: Attachable&lt;&#39;a&gt;&gt;(value: &amp;&#39;a String) {
    let mut bla = T::new();
    bla.attach(&amp;value);
}

would solve the problem since now the lifetime is detected correctly again since it is part of the input reference argument. But this wouldn't be a suitable solution for me. The do_stuff function shall handle all the logic inside the function without requiring any function arguments. Only generic arguments are allowed like lifetimes and types.

I assume that I may have to use Higher-Rank Trait Bounds and implement do_stuff like:

fn do_stuff&lt;T&gt;()
where
    T: for&lt;&#39;a&gt; Attachable&lt;&#39;a&gt;,
{
    let mut object = T::new();
    let value: String = &quot;hello&quot;.to_string();
    object.attach(&amp;value);
}

but this causes rust to complain that the implementation of Attachable is not generic enough for SomeAttachable.

To provide some context: I require this for unit tests based on the generic-test crate where the behavior of a trait is verified.

答案1

得分: 2

你可以使用一个AttachableTag特质,它具有一个通用的关联类型,为任何'a提供了一个Attachable&lt;&#39;a&gt;(你也可以使用相同的特质和类型,但我更喜欢这种方式):

trait AttachableTag {
    type Attachable&lt;&#39;b&gt;: Attachable&lt;&#39;b&gt;;
}

trait Attachable&lt;&#39;a&gt; {
    fn new() -&gt; Self;
    fn attach(&amp;mut self, value: &amp;&#39;a String);
}

struct SomeAttachableTag;

impl AttachableTag for SomeAttachableTag {
    type Attachable&lt;&#39;b&gt; = SomeAttachable&lt;&#39;b&gt;;
}

struct SomeAttachable&lt;&#39;a&gt; {
    id: Option&lt;&amp;&#39;a String&gt;,
}

impl&lt;&#39;a&gt; Attachable&lt;&#39;a&gt; for SomeAttachable&lt;&#39;a&gt; {
    fn new() -&gt; Self {
        Self { id: None }
    }
    fn attach(&amp;mut self, value: &amp;&#39;a String) {
        self.id = Some(value)
    }
}

fn do_stuff&lt;T: AttachableTag&gt;() {
    let mut object = T::Attachable::new();
    let value: String = &quot;hello&quot;.to_string();
    object.attach(&amp;value);
}

do_stuff::&lt;SomeAttachableTag&gt;();

然而,这会导致错误:

error[E0597]: `value` does not live long enough
  --> src/main.rs:32:19
   |
32 |     object.attach(&amp;value);
   |                   ^^^^^^ borrowed value does not live long enough
33 | }
   | -
   | |
   | `value` dropped here while still borrowed
   | borrow might be used here, when `object` is dropped and runs the destructor for type `<T as AttachableTag>::Attachable&lt;&#39;_&gt;`
   |
   = note: values in a scope are dropped in the opposite order they are defined

要解决这个问题,将value的声明移动到object之前:

fn do_stuff&lt;T: AttachableTag&gt;() {
    let value: String = &quot;hello&quot;.to_string();
    let mut object = T::Attachable::new();
    object.attach(&amp;value);
}
英文:

You can use a AttachableTag trait, with a generic associated type that gives you an Attachable&lt;&#39;a&gt; for any &#39;a (you can also use the same trait and type, but I prefer it this way):

trait AttachableTag {
    type Attachable&lt;&#39;b&gt;: Attachable&lt;&#39;b&gt;;
}

trait Attachable&lt;&#39;a&gt; {
    fn new() -&gt; Self;
    fn attach(&amp;mut self, value: &amp;&#39;a String);
}

struct SomeAttachableTag;

impl AttachableTag for SomeAttachableTag {
    type Attachable&lt;&#39;b&gt; = SomeAttachable&lt;&#39;b&gt;;
}

struct SomeAttachable&lt;&#39;a&gt; {
    id: Option&lt;&amp;&#39;a String&gt;,
}

impl&lt;&#39;a&gt; Attachable&lt;&#39;a&gt; for SomeAttachable&lt;&#39;a&gt; {
    fn new() -&gt; Self {
        Self { id: None }
    }
    fn attach(&amp;mut self, value: &amp;&#39;a String) {
        self.id = Some(value)
    }
}

fn do_stuff&lt;T: AttachableTag&gt;() {
    let mut object = T::Attachable::new();
    let value: String = &quot;hello&quot;.to_string();
    object.attach(&amp;value);
}

do_stuff::&lt;SomeAttachableTag&gt;();

However, this gives an error:

error[E0597]: `value` does not live long enough
  --&gt; src/main.rs:32:19
   |
32 |     object.attach(&amp;value);
   |                   ^^^^^^ borrowed value does not live long enough
33 | }
   | -
   | |
   | `value` dropped here while still borrowed
   | borrow might be used here, when `object` is dropped and runs the destructor for type `&lt;T as AttachableTag&gt;::Attachable&lt;&#39;_&gt;`
   |
   = note: values in a scope are dropped in the opposite order they are defined

To fix that, move value's declaration before object:

fn do_stuff&lt;T: AttachableTag&gt;() {
    let value: String = &quot;hello&quot;.to_string();
    let mut object = T::Attachable::new();
    object.attach(&amp;value);
}

huangapple
  • 本文由 发表于 2023年2月10日 15:57:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75408300.html
匿名

发表评论

匿名网友

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

确定