我正在寻求了解如何解决 Rust 中的终身问题。关于 SO、ofc 有很多类似的问题,但没有一个看起来完全相同——所有其他问题似乎都与结构或“静态”有关,我的问题是关于我从外部库使用的一个开放式特征生命周期。
我正在使用tokio_postgres从数据库中查询行,就像人们所做的那样。我正在尝试简化一些我经常做的操作。从查询中获取 UUID 集合很容易:
pub fn rows_as_first(rows: Vec<Row>) -> Vec<Uuid> {
rows.into_iter().map(|row| row.get(0)).collect()
}
效果很好。但是当我尝试使其通用时(例如收集字符串或其他东西):
pub fn rows_as_first_generic<'a, T: FromSql<'a>>(rows: Vec<Row>) -> Vec<T> {
rows.into_iter().map(|row| row.get(0)).collect()
}
我收到错误:
error[E0597]: `row` does not live long enough
--> backend-axum/src/queries.rs:64:32
|
63 | pub fn rows_as_first_generic<'a, T: FromSql<'a>>(rows: Vec<Row>) -> Vec<T> {
| -- lifetime `'a` defined here
64 | rows.into_iter().map(|row| row.get(0)).collect()
| --- ^^^-------
| | | |
| | | `row` dropped here while still borrowed
| | borrowed value does not live long enough
| | argument requires that `row` is borrowed for `'a`
| binding `row` declared here
现在,我完全明白了row
只在关闭期间存在,这是完全有道理的。我不明白的是:
这个函数的 Uuid 版本有什么不同,它不会提示?我认为有一些特征使它神奇地工作,我想知道 a)它是什么以及 b)将来在尝试在 Rust 中重构这样的代码时如何发现这些信息。 (这不是我第一次在重构时遇到类似的问题。)
我该如何为引用添加生命周期:a) 不是引用,因为我拥有它;b) 当我根本不需要引用时,我想拥有最终结果也是如此。
我对 T: FromSql<'a>
表示怀疑——或许还需要进一步澄清。我需要它 get(0)
调用,根据定义,我返回的是来自 sql 查询的某个值。但我不想要引用,我想要拥有最终的结果。我很高兴做一个clone()
为了实现这一点,这似乎不是这里的问题。 (当然,我尝试添加 clone()
。)
所以,不知何故,我应该在行上有一个生命周期说明符,但 tokio_postgres 的工作方式是:
client.query(&stmt, params).await?
返回Vec<Row>
不包括生命周期,因为它是被拥有的。那么……我该怎么办? Uuid 示例如何工作?我确信我在这里遗漏了一些基本且明显的东西,所以......抱歉。
谢谢。
最佳答案
很好的问题!让我先尝试回答你的第二个问题。你说得对,它的核心是T: FromSql<'a>
。此签名:
pub fn rows_as_first_generic<'a, T: FromSql<'a>>
有'a
作为输入生命周期,这意味着签名表示:“对于'a
的调用者选择的任何生命周期rows_as_first_generic
,T
必须可以从postgres创建为 'a
而存在的值(value)观”。这不是您想要的:您想要一个 T
它是由一个生命周期为 rows_as_first_generic
的 postgres 值创建的。选择(不是它的调用者)——即 get
的生命周期在函数正文中调用您提到的内容。在 Rust 中编写此代码的方式是:
pub fn rows_as_first_generic<T: for<'a> FromSql<'a>>
这表示:T 必须可以从具有任何生命周期的 postgres 值创建。 (即 T
必须在任何生命周期内实现 FromSql<'a>
'a
)。因此,特别是,它可以从 postgres 值创建,该值与您的行一样长。
对于 Uuid 为何有效以及如何发现差异的第一个问题:它对 Uuid 有效的原因是 Uuid 确实在任何生命周期内实现 FromSql(如您所见 in the tokio_postgres docs )。
除了练习这些模式并建立关于哪些错误建议哪些解决方案的直觉之外,我不确定我还有什么可以建议的。在这种情况下,如果特征边界需要在某个输入生命周期内借用一些临时值(这是上面得到的错误),有时是因为您确实想要 HRTB如本例所示。另一种选择是采取 &'a Vec<Row>
或类似的,在这种情况下您可以使用输入生命周期。即这也有效:
pub fn rows_as_first_generic_input<'a, T: FromSql<'a>>(rows: &'a Vec<Row>) -> Vec<T>
因为现在调用者保证您可以保留 Row 的时间长度与 T 需要的时间长度相同(而不是原始代码中短暂的临时生命周期)。希望对您有所帮助!
关于rust - 返回特征的生命周期不存在于 tokio_postgres 行上并且不存在 "live long enough",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77682165/