如何在Rust中声明除生命周期外类型相同的通用参数?

5

我写了下面的代码,但是无法编写生命周期约束以使其正常工作,并出现错误:

use futures::Future;

async fn foo<'a>(a: &'a str) -> &'a str {
    let task = get();
    f(a, task).await
}

async fn f<T>(v: T, task: impl Future<Output = T>) -> T {
    if true {
        v
    } else {
        task.await
    }
}

async fn get() -> &'static str {
    "foo"
}

错误:

error[E0759]: `a` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
 --> src/lib.rs:3:18
  |
3 | async fn foo<'a>(a: &'a str) -> &'a str {
  |                  ^  ------- this data with lifetime `'a`...
  |                  |
  |                  ...is captured here...
4 |     let task = get();
5 |     f(a, task).await
  |     - ...and is required to live as long as `'static` here

沙盒

我认为,如果函数 f 中的两个参数可以有各自不同的生命周期,那么问题就可以解决。 例如:

v: T,
task: S,
T: 'a,
S: 'b,
'b: 'a,
S == T

如何解决这个问题?


你的意思是:fn f<'a, 'b: 'a, T>(v: &'a T, task: impl Future<Output = &'b T>) -> &'a T - Jmb
不行。T 是一个包含生命周期参数的结构体,就像 Cow<'a, str> 一样。因此我不能将 T 重写为 &'a T - tamuhey
1
你的示例代码可以通过使用第二个类型参数 U 修复(输出类型为 Future)。你的示例代码没有展示出为什么 TU 需要除了生命周期以外是相同的类型。你的问题只阐述了一个“尝试解决”问题的方案,但并未说明实际问题是什么。很可能解决方案不在于声明两个泛型类型参数,这两个参数除了生命周期以外几乎是相同的,但是如果没有更多信息我们无法得知真正的问题所在。 - Sven Marnach
谢谢。我修复了我的示例。我面临的真正问题更加复杂,而且我不想在这里粘贴代码,因为我想展示MVCE。 - tamuhey
我想知道为什么编译器会抱怨 a 的生命周期必须是 static,尽管 T: 'a 是必要且充分的。 - tamuhey
显示剩余2条评论
1个回答

7
使用函数接口而不是异步函数,可以通过另一个最小示例来重现相同的问题。
fn get() -> impl FnOnce() -> &'static str {
    || "foo"
}

fn foo<'a, T: 'a, F>(_: &'a str, _: F)
where
    F: Fn() -> T,
    T: FnOnce() -> &'a str,
{
}

let x = "".to_string();
foo(&*x, &get);

error[E0597]: `x` does not live long enough
  --> src/main.rs:22:11
   |
22 |     foo(&*x, &get);
   |     ------^-------
   |     |     |
   |     |     borrowed value does not live long enough
   |     argument requires that `x` is borrowed for `'static`
23 | }
   | - `x` dropped here while still borrowed

此示例允许我们将get转换为函数参数,并观察到传递此函数会强制执行生命周期'a'static硬约束。尽管程序的意图是最好的,但返回供应商函数(或promise)的函数并不提供关于输出生命周期的协变性。也就是说,() -> &'static str不能满足for<'a> () -> &'a str。偶尔,编译器会回退并建议您坚持最薄弱的链接即'static生命周期,即使这可能并不可取。
请注意,目前表示泛型生命周期的类型的手段非常有限。这些是一种高阶类型,仅可以通过高阶特征边界的某个表达水平来指定(一旦它们被完全实现和稳定下来,还可以使用通用关联类型)。在这种情况下,与其尝试使f适用于T<'a>,不如将我们的get泛型化为生命周期'a。然后可以在实现处进行子类型处理,因为我们知道字符串字面值可以满足任何生命周期要求。
fn get<'a>() -> impl FnOnce() -> &'a str {
    || "foo"
}

在异步(async)情况下(Playground):
async fn get<'a>() -> &'a str {
    "foo"
}

参见:


感谢您的出色回答!顺便提一下,我简化了您的示例:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d571d3cc6c68a7070a9c855f12c5aec3 - tamuhey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接