我正在寻求了解如何解决 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
作为 input 生命周期,这意味着签名表示:“对于 'a
的 调用者选择的任何生命周期 rows_as_first_generic
,T
必须可以从生命周期的 postgres 值创建'a
”。这不是你想要的:你想要一个 T
,它可以从 postgres 值创建,该值的生命周期由 rows_as_first_generic
选择(不是它的调用者)——即你在 中提到的 get
调用的生命周期你的函数体。你用 Rust 写的方式是:
pub fn rows_as_first_generic<T: for<'a> FromSql<'a>>
这表示:T 必须可以从具有 any 生命周期的 postgres 值创建。 (即
T
必须在任何生命周期内实现 FromSql<'a>
'a
)。因此,特别是,它可以根据 postgres 值创建,该值与您的行一样长。
对于 Uuid 为何有效以及如何发现差异的第一个问题:它对 Uuid 有效的原因是 Uuid 确实在任何生命周期中实现 FromSql(如您在 tokio_postgres 文档中看到的)。
除了练习这些模式并建立关于哪些错误建议哪些解决方案的直觉之外,我不确定我还有什么可以建议的。在这种情况下,如果特征边界需要在某个输入生命周期内借用一些临时值(这是您在上面得到的错误),有时是因为您确实想要一个HRTB,如本例所示。另一种选择是采用 &'a Vec<Row>
或类似的,在这种情况下您可以使用输入生命周期。即这也有效:
pub fn rows_as_first_generic_input<'a, T: FromSql<'a>>(rows: &'a Vec<Row>) -> Vec<T>
因为现在调用者保证您可以保留 Row 的时间长度与 T 需要它的时间长度相同(而不是原始代码中短暂的临时生命周期)。希望对您有帮助!
必须对于任何此类类型都有效。但考虑一下 impl<'a> FromSql<'a> for &'a [u8]
Uuid
版本中,编译器不需要支持所有可能性,而只需要支持一种 -
Uuid
。执行此操作的不是某种 Trait,而是此特定实现返回的值不再与行的生命周期相关联的事实。
T
不是参考的断言是不正确的。从签名中我们不知道。但如果您想使用这个通用版本,我建议改为传递对
rows
变量的引用。您可以说“无论出现什么参考,在
rows
的整个生命周期内都有效”。
pub fn rows_as_first_generic<'a, T: FromSql<'a>>(rows: &'a [Row]) -> Vec<T> {
rows.iter().map(|row| row.get(0)).collect()
}
PS。有关生命周期注释的进一步阅读:https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax