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

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

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

问题

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

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

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

  1. 一个简单的实现可能如下所示:
  2. ```rust
  3. struct SomeAttachable<'a> {
  4. id: Option<&'a String>,
  5. }
  6. impl<'a> Attachable<'a> for SomeAttachable<'a> {
  7. fn new() -> Self {
  8. Self { id: None }
  9. }
  10. fn attach(&mut self, value: &'a String) {
  11. self.id = Some(value);
  12. }
  13. }

并且可以直接使用它。

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

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

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

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

将函数签名调整为:

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

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

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

  1. fn do_stuff<T>()
  2. where
  3. T: for<'a> Attachable<'a>,
  4. {
  5. let mut object = T::new();
  6. let value: String = "hello".to_string();
  7. object.attach(&value);
  8. }

但这会导致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.

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

A simplistic implementation could look like:

  1. struct SomeAttachable&lt;&#39;a&gt; {
  2. id: Option&lt;&amp;&#39;a String&gt;,
  3. }
  4. impl&lt;&#39;a&gt; Attachable&lt;&#39;a&gt; for SomeAttachable&lt;&#39;a&gt; {
  5. fn new() -&gt; Self {
  6. Self { id: None }
  7. }
  8. fn attach(&amp;mut self, value: &amp;&#39;a String) {
  9. self.id = Some(value)
  10. }
  11. }

and using it works out of the box.

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

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

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

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:

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

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:

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

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;(你也可以使用相同的特质和类型,但我更喜欢这种方式):

  1. trait AttachableTag {
  2. type Attachable&lt;&#39;b&gt;: Attachable&lt;&#39;b&gt;;
  3. }
  4. trait Attachable&lt;&#39;a&gt; {
  5. fn new() -&gt; Self;
  6. fn attach(&amp;mut self, value: &amp;&#39;a String);
  7. }
  8. struct SomeAttachableTag;
  9. impl AttachableTag for SomeAttachableTag {
  10. type Attachable&lt;&#39;b&gt; = SomeAttachable&lt;&#39;b&gt;;
  11. }
  12. struct SomeAttachable&lt;&#39;a&gt; {
  13. id: Option&lt;&amp;&#39;a String&gt;,
  14. }
  15. impl&lt;&#39;a&gt; Attachable&lt;&#39;a&gt; for SomeAttachable&lt;&#39;a&gt; {
  16. fn new() -&gt; Self {
  17. Self { id: None }
  18. }
  19. fn attach(&amp;mut self, value: &amp;&#39;a String) {
  20. self.id = Some(value)
  21. }
  22. }
  23. fn do_stuff&lt;T: AttachableTag&gt;() {
  24. let mut object = T::Attachable::new();
  25. let value: String = &quot;hello&quot;.to_string();
  26. object.attach(&amp;value);
  27. }
  28. do_stuff::&lt;SomeAttachableTag&gt;();

然而,这会导致错误:

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

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

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

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):

  1. trait AttachableTag {
  2. type Attachable&lt;&#39;b&gt;: Attachable&lt;&#39;b&gt;;
  3. }
  4. trait Attachable&lt;&#39;a&gt; {
  5. fn new() -&gt; Self;
  6. fn attach(&amp;mut self, value: &amp;&#39;a String);
  7. }
  8. struct SomeAttachableTag;
  9. impl AttachableTag for SomeAttachableTag {
  10. type Attachable&lt;&#39;b&gt; = SomeAttachable&lt;&#39;b&gt;;
  11. }
  12. struct SomeAttachable&lt;&#39;a&gt; {
  13. id: Option&lt;&amp;&#39;a String&gt;,
  14. }
  15. impl&lt;&#39;a&gt; Attachable&lt;&#39;a&gt; for SomeAttachable&lt;&#39;a&gt; {
  16. fn new() -&gt; Self {
  17. Self { id: None }
  18. }
  19. fn attach(&amp;mut self, value: &amp;&#39;a String) {
  20. self.id = Some(value)
  21. }
  22. }
  23. fn do_stuff&lt;T: AttachableTag&gt;() {
  24. let mut object = T::Attachable::new();
  25. let value: String = &quot;hello&quot;.to_string();
  26. object.attach(&amp;value);
  27. }
  28. do_stuff::&lt;SomeAttachableTag&gt;();

However, this gives an error:

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

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

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

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:

确定