参数类型可能不会存活足够长时间(再次?)(在闭包中)

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

the parameter type may not live long enough (again?) (in closure)

问题

  1. 在尝试编译以下代码时:
  2. ```rust
  3. fn make<DomainType>(foo: fn(DomainType) -> ()) -> Box<dyn Fn(DomainType) -> ()> {
  4. let foo_clone = foo.clone();
  5. let bar = move |input: DomainType| {foo_clone(input)};
  6. Box::new(bar)
  7. }
  8. fn main() {}

编译器报错如下:

  1. error[E0310]: 参数类型 `DomainType` 可能无法存活足够长时间
  2. --&gt; src/main.rs:4:5
  3. |
  4. 4 | Box::new(bar)
  5. | ^^^^^^^^^^^^^ ...以满足类型 `[closure@src/main.rs:3:15: 3:58]` 的必需生命周期边界
  6. |
  7. 帮助:考虑添加一个显式的生命周期边界...
  8. |
  9. 1 | fn make<DomainType: 'static>(foo: fn(DomainType) -> ()) -> Box<dyn Fn(DomainType) -> ()> {
  10. | +++++++++
  11. 要了解更多关于此错误的信息,请尝试运行 `rustc --explain E0310`。

我一直在阅读类似问题的解答:

https://stackoverflow.com/questions/29740488/parameter-type-may-not-live-long-enough

https://stackoverflow.com/questions/40053550/the-compiler-suggests-i-add-a-static-lifetime-because-the-parameter-type-may-no

但在这两个示例中,需要添加生命周期的是包装的 trait。而在这种情况下,似乎只是闭包 trait 的 DomainType 需要生命周期。我对此感到非常困惑。

我对函数定义中生命周期变量的理解是,它们将返回类型的生命周期(在此处可以理解为隐含为静态,或者至少盒子中的内容是静态的)与函数参数的生命周期(可能是省略的)连接起来。但在这里没有任何函数参数是 DomainType

  1. <details>
  2. <summary>英文:</summary>
  3. In trying to compile the code

fn make<DomainType>(foo: fn(DomainType) -> ()) -> Box<dyn Fn(DomainType) -> ()> {
let foo_clone = foo.clone();
let bar = move |input: DomainType| {foo_clone(input)};
Box::new(bar)
}

fn main() {}

  1. The compiler gives

error[E0310]: the parameter type DomainType may not live long enough
--> src/main.rs:4:5
|
4 | Box::new(bar)
| ^^^^^^^^^^^^^ ...so that the type [closure@src/main.rs:3:15: 3:58] will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound...
|
1 | fn make<DomainType: 'static>(foo: fn(DomainType) -> ()) -> Box<dyn Fn(DomainType) -> ()> {
| +++++++++

For more information about this error, try rustc --explain E0310.

  1. I have been reading through similar questions
  2. https://stackoverflow.com/questions/29740488/parameter-type-may-not-live-long-enough
  3. and
  4. https://stackoverflow.com/questions/40053550/the-compiler-suggests-i-add-a-static-lifetime-because-the-parameter-type-may-no
  5. But in both of those examples, the boxed trait is what needs the added lifetime. In this case it&#39;s apparently just the DomainType of the closure trait. I&#39;m quite baffled what to make of this.
  6. My understanding of lifetime variables in function definitions is that they connect the lifetime of the returned type (which I can understand is implicitly static here, or at least the thing inside the box is) to those (perhaps elided) lifetimes of the function parameters. But no function parameter here is a `DomainType`.
  7. </details>
  8. # 答案1
  9. **得分**: 3
  10. 免责声明:这个问题涉及到 Rust 编译器的具体细节,我对我在这里说的一切都不是百分之百确定的。请谨慎对待所有内容。
  11. 问题在于,如果没有注释寿命(lifetime),`Box&lt;T&gt;` 表示 `T` `&#39;static`
  12. 然而,泛型不一定是这样的。泛型可以包含任何类型的寿命。为了匹配两者,你需要明确注释寿命。
  13. ## 解决方案 1
  14. 你**可以**为泛型添加 `&#39;static` 注释,如下所示:
  15. ```rust
  16. fn make&lt;DomainType: &#39;static&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType)&gt; {
  17. Box::new(foo)
  18. }

然而,如果你实际上遇到了泛型带有寿命的情况,这会导致问题:

  1. fn my_func(x: &amp;str) {
  2. println!("my_func: {}", x);
  3. }
  4. fn main() {
  5. let x = "abc".to_string();
  6. let f = make(my_func);
  7. f(&amp;x);
  8. }
  1. error[E0597]: `x` does not live long enough
  2. --> src/main.rs:13:7
  3. |
  4. 13 | f(&amp;x);
  5. | --^^-
  6. | | |
  7. | | borrowed value does not live long enough
  8. | argument requires that `x` is borrowed for `&#39;static`
  9. 14 | }
  10. | - `x` dropped here while still borrowed

解决方案 2

在这里的正确方式是通过告诉编译器泛型附加的寿命与包含在 Box 中的寿命匹配来解决初始不匹配的问题。

像这样:

  1. fn make&lt;&#39;a, DomainType: &#39;a&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType) + &#39;a&gt; {
  2. Box::new(foo)
  3. }

现在它可以工作了:

  1. fn my_func(x: &amp;str) {
  2. println!("my_func: {}", x);
  3. }
  4. fn main() {
  5. let x = "abc".to_string();
  6. let f = make(my_func);
  7. f(&amp;x);
  8. }
  1. my_func: abc

问题

但是这仍然存在问题。我不确定这些问题如何解决。

问题在于生成的 Box 对象将始终与特定寿命绑定。例如,看看这个例子:

  1. fn make&lt;&#39;a, DomainType: &#39;a&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType) + &#39;a&gt; {
  2. Box::new(foo)
  3. }
  4. fn my_func(x: &amp;str) {
  5. println!("my_func: {}", x);
  6. }
  7. fn make2() -&gt; Box&lt;dyn Fn(&amp;str)&gt; {
  8. Box::new(|x| println!("closure: {}", x))
  9. }
  10. fn main() {
  11. let x = "abc".to_string();
  12. let f = make(my_func);
  13. let f2 = make2();
  14. let fs = [f2, f];
  15. }
  1. error[E0308]: mismatched types
  2. --> src/main.rs:19:19
  3. |
  4. 19 | let fs = [f2, f];
  5. | ^ one type is more general than the other
  6. |
  7. = note: expected trait object `dyn for&lt;&#39;r&gt; Fn(&amp;&#39;r str)`
  8. found trait object `dyn Fn(&amp;str)`

f2 可以接受可以借用的所有对象。f 只能接受具有相同寿命的对象,因为 &#39;a 在调用 make 时解析,而不是在执行 f 时。

另一个例子是这样的:

  1. fn make&lt;&#39;a, DomainType: &#39;a&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType) + &#39;a&gt; {
  2. Box::new(foo)
  3. }
  4. fn my_func(x: &amp;str) {
  5. println!("my_func: {}", x);
  6. }
  7. fn make2() -&gt; Box&lt;dyn Fn(&amp;str)&gt; {
  8. Box::new(|x| println!("closure: {}", x))
  9. }
  10. fn main() {
  11. let f = make(my_func);
  12. let f2 = make2();
  13. {
  14. let x = "abc".to_string();
  15. f2(&amp;x); // 可以工作
  16. f(&amp;x); // 无法编译通过
  17. }
  18. }
  1. error[E0597]: `x` does not live long enough
  2. --> src/main.rs:20:11
  3. |
  4. 20 | f(&amp;x); // 无法编译通过
  5. | ^^ borrowed value does not live long enough
  6. 21 | }
  7. | - `x` dropped here while still borrowed
  8. 22 | }
  9. | - borrow might be used here, when `f` is dropped and runs the destructor for type `Box&lt;dyn Fn(&amp;str)&gt;`
  10. |
  11. = note: values in a scope are dropped in the opposite order they are defined

我认为这是不可解决的。我认为泛型不能携带通用寿命,它们只能携带特定的寿命。并且特定寿命在调用 make() 时解析,而不是在执行 f 时。

虽然一个详细的解释需要一个对 Rust 编译器有更深入了解的人。如果像这样的东西存在的话,我也会对它感兴趣(类似于 DomainType: for&lt;&#39;a&gt; 或类似的东西)。

我可以看到一个示例的输出中说需要 dyn for&lt;&#39;r&gt; Fn(&amp;&#39;r str)。但我不知道是否可以手动注释这样的东西,或者它是否是编译器的内部内容。但这将是所需的内容 - 不是 Box&lt;dyn Fn(DomainType) + &#39;a&gt;,而是需要类

英文:

DISCLAIMER: This question goes very deep into Rust compiler specifics, and I'm not 100% sure about everything I say here. Take everything with a grain of salt.


The problem here is that Box&lt;T&gt; means that T is &#39;static if no lifetime is annotated.

A generic, however, does not mean that. A generic could contain any kind of lifetime. To match the two, you need an explicit lifetime annotation.

Solution 1

You could annotate &#39;static to the generic, like this:

  1. fn make&lt;DomainType: &#39;static&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType)&gt; {
  2. Box::new(foo)
  3. }

That, however, would create problems if you do actually encounter the case where the generic carries a lifetime:

  1. fn my_func(x: &amp;str) {
  2. println!(&quot;my_func: {}&quot;, x);
  3. }
  4. fn main() {
  5. let x = &quot;abc&quot;.to_string();
  6. let f = make(my_func);
  7. f(&amp;x);
  8. }
  1. error[E0597]: `x` does not live long enough
  2. --&gt; src/main.rs:13:7
  3. |
  4. 13 | f(&amp;x);
  5. | --^^-
  6. | | |
  7. | | borrowed value does not live long enough
  8. | argument requires that `x` is borrowed for `&#39;static`
  9. 14 | }
  10. | - `x` dropped here while still borrowed

Solution 2

The proper way here is to solve the initial mismatch by telling the compiler that the lifetimes attached to the generic match the lifetimes contained in the Box.

Like this:

  1. fn make&lt;&#39;a, DomainType: &#39;a&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType) + &#39;a&gt; {
  2. Box::new(foo)
  3. }

And it now works:

  1. fn my_func(x: &amp;str) {
  2. println!(&quot;my_func: {}&quot;, x);
  3. }
  4. fn main() {
  5. let x = &quot;abc&quot;.to_string();
  6. let f = make(my_func);
  7. f(&amp;x);
  8. }
  1. my_func: abc

Problems

There are still problems with this. I'm unsure how/if they are solvable, though.

The problem is that the resulting Box object will always be bound to a specific lifetime. Look for example at this:

  1. fn make&lt;&#39;a, DomainType: &#39;a&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType) + &#39;a&gt; {
  2. Box::new(foo)
  3. }
  4. fn my_func(x: &amp;str) {
  5. println!(&quot;my_func: {}&quot;, x);
  6. }
  7. fn make2() -&gt; Box&lt;dyn Fn(&amp;str)&gt; {
  8. Box::new(|x| println!(&quot;closure: {}&quot;, x))
  9. }
  10. fn main() {
  11. let x = &quot;abc&quot;.to_string();
  12. let f = make(my_func);
  13. let f2 = make2();
  14. let fs = [f2, f];
  15. }
  1. error[E0308]: mismatched types
  2. --&gt; src/main.rs:19:19
  3. |
  4. 19 | let fs = [f2, f];
  5. | ^ one type is more general than the other
  6. |
  7. = note: expected trait object `dyn for&lt;&#39;r&gt; Fn(&amp;&#39;r str)`
  8. found trait object `dyn Fn(&amp;str)`

f2 here takes all objects that can be borrowed. f only takes objects that all can be borrowed with the same lifetime, as &#39;a resolves when make is called, not when f is executed.

Another example is this:

  1. fn make&lt;&#39;a, DomainType: &#39;a&gt;(foo: fn(DomainType)) -&gt; Box&lt;dyn Fn(DomainType) + &#39;a&gt; {
  2. Box::new(foo)
  3. }
  4. fn my_func(x: &amp;str) {
  5. println!(&quot;my_func: {}&quot;, x);
  6. }
  7. fn make2() -&gt; Box&lt;dyn Fn(&amp;str)&gt; {
  8. Box::new(|x| println!(&quot;closure: {}&quot;, x))
  9. }
  10. fn main() {
  11. let f = make(my_func);
  12. let f2 = make2();
  13. {
  14. let x = &quot;abc&quot;.to_string();
  15. f2(&amp;x); // Works
  16. f(&amp;x); // Fails to compile
  17. }
  18. }
  1. error[E0597]: `x` does not live long enough
  2. --&gt; src/main.rs:20:11
  3. |
  4. 20 | f(&amp;x); // Fails to compile
  5. | ^^ borrowed value does not live long enough
  6. 21 | }
  7. | - `x` dropped here while still borrowed
  8. 22 | }
  9. | - borrow might be used here, when `f` is dropped and runs the destructor for type `Box&lt;dyn Fn(&amp;str)&gt;`
  10. |
  11. = note: values in a scope are dropped in the opposite order they are defined

I don't think this is solvable, though. I think generics can't carry generic lifetimes, they can only carry specific lifetimes. And said specific lifetime gets resolved at the point where make() is called.

Although a proper explanation would require someone with a deeper Rust compiler understanding than me. If something like this exists, I'd be interested in it as well. (something like DomainType: for&lt;&#39;a&gt; or similar)

I can see that one of the example's output says it would require a dyn for&lt;&#39;r&gt; Fn(&amp;&#39;r str). I don't know, however, if something like this is annotatable by hand or if it's something internal to the compiler. But that would be what is required - instead of Box&lt;dyn Fn(DomainType) + &#39;a&gt;, we would need something like Box&lt;dyn for &lt;&#39;r&gt; Fn(DomainType + &#39;r)&gt;. Again, I don't know if/how this is annotatable, and if it is, I'd also be interested in it.

答案2

得分: 0

"Could Higher Rank Trait Bounds be what you're looking for?"

如果您正在寻找的是“高阶特质边界”,那么这可能是您需要的吗?

  1. fn make<F, DomainType>(foo: F) -> Box<dyn Fn(DomainType)>
  2. where
  3. for<'r> F: Fn(DomainType) + Clone + 'r
  4. {
  5. let foo_clone = foo.clone();
  6. let bar = move |input: DomainType| {foo_clone(input)};
  7. Box::new(bar)
  8. }

如果函数的目的是消耗一个闭包并将其返回为Box,我认为您不需要Clone特质。事实上,它甚至不需要嵌套在一个新的闭包中。只需直接放入Box中。

  1. fn make<F, DomainType>(foo: F) -> Box<dyn Fn(DomainType)>
  2. where
  3. for<'r> F: Fn(DomainType) + 'r
  4. {
  5. Box::new(foo)
  6. }

除非您想传递一个不会被消耗的闭包的引用:

  1. fn make<F, DomainType>(foo: &F) -> Box<dyn Fn(DomainType)>
  2. where
  3. for<'r> F: Fn(DomainType) + Clone + 'r
  4. {
  5. Box::new(foo.clone())
  6. }
英文:

Could Higher Rank Trait Bounds be what you're looking for?

  1. fn make&lt;F, DomainType&gt;(foo: F) -&gt; Box&lt;dyn Fn(DomainType)&gt;
  2. where
  3. for&lt;&#39;r&gt; F: Fn(DomainType) + Clone + &#39;r
  4. {
  5. let foo_clone = foo.clone();
  6. let bar = move |input: DomainType| {foo_clone(input)};
  7. Box::new(bar)
  8. }

If the purpose of the function is to consume a closure and return it in a Box, I don't think you need the Clone trait. In fact, it doesn't even need to be nested in a new closure. Just directly box it.

  1. fn make&lt;F, DomainType&gt;(foo: F) -&gt; Box&lt;dyn Fn(DomainType)&gt;
  2. where
  3. for&lt;&#39;r&gt; F: Fn(DomainType) + &#39;r
  4. {
  5. Box::new(foo)
  6. }

Unless you want to pass in a reference to a closure that doesn't get consumed:

  1. fn make&lt;F, DomainType&gt;(foo: &amp;F) -&gt; Box&lt;dyn Fn(DomainType)&gt;
  2. where
  3. for&lt;&#39;r&gt; F: Fn(DomainType) + Clone + &#39;r
  4. {
  5. Box::new(foo.clone())
  6. }

huangapple
  • 本文由 发表于 2023年1月9日 08:15:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/75052215.html
匿名

发表评论

匿名网友

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

确定