使用SQLx解码泛型类型时的生命周期处理问题
问题:SQLx泛型查询的生命周期编译错误
我尝试用泛型方法从结构一致但某列类型不同的SQLite表中取值,编写了查询方法后编译失败。
核心代码
async fn query<'a, 'r, T: DatabaseType<Item=T> + Decode<'r, Sqlite> + Type<Sqlite>>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> { let mut connection = self.pool.acquire().await?; let mut rows = sqlx::query("Select id, value from table where name = $1") .bind(name) .fetch(&mut connection); let mut results = Vec::new(); while let Some(row) = rows.try_next().await? { results.push(NamedValue { name, value: row.try_get("value")? }) } Ok(results) }
编译错误
borrowed value does not live long enough, argument requires that 'row' is borrowed for 'r
问题在于sqlx::Decode要求的生命周期'r被声明为函数签名的一部分,但这个生命周期仅对应迭代行时的临时资源。不能去掉该约束,因为try_get需要类型实现Decode才能工作。另外,解码后的值生命周期是static,需要让编译器确认解码时row的生命周期足够。
完整可复现代码
Cargo.toml
[package] name = "sqlx-minimal-example" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["full"] } sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite"] } anyhow = "1.0" futures = "0.3"
src/main.rs
use anyhow::Result; use sqlx::{Decode, Row, Sqlite, SqlitePool, Type}; use futures::TryStreamExt; #[tokio::main] async fn main() -> Result<()> { println!("Hello, world!"); Ok(()) } struct NamedValue<'a ,T> { name: &'a str, value: T } struct SqliteBackend { pool: SqlitePool } trait DatabaseType { type Item; } impl DatabaseType for f32 { type Item = f32; } impl DatabaseType for i32 { type Item = i32; } impl SqliteBackend { async fn query<'a, 'r, T: DatabaseType<Item=T> + Decode<'r, Sqlite> + Type<Sqlite>>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> { let mut connection = self.pool.acquire().await?; let mut rows = sqlx::query("Select id, value from table where name = $1") .bind(name) .fetch(&mut connection); let mut results = Vec::new(); while let Some(row) = rows.try_next().await? { results.push(NamedValue { name, value: row.try_get("value")? }) } Ok(results) } }
解决方案
问题根源是函数签名中的'r生命周期被绑定到了整个函数外部,但实际上'r只需要在解码单个row的瞬间有效,且解码后的T不持有row的引用。可以通过**高阶生命周期(Higher-Ranked Trait Bounds, HRTBs)**约束T对任意生命周期'r都实现Decode<'r, Sqlite>,无需将'r暴露在函数签名中。
修改后的query函数签名如下:
async fn query<'a, T>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> where T: DatabaseType<Item = T> + for<'r> Decode<'r, Sqlite> + Type<Sqlite>,
解释
for<'r> Decode<'r, Sqlite>表示:对于任意生命周期'r,T都能实现Decode<'r, Sqlite>。这告诉编译器,无论row的生命周期多短,T都能从其中解码,且解码后的T不会持有row的引用,符合值为static生命周期的前提。
替换后的完整impl SqliteBackend代码:
impl SqliteBackend { async fn query<'a, T>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> where T: DatabaseType<Item = T> + for<'r> Decode<'r, Sqlite> + Type<Sqlite>, { let mut connection = self.pool.acquire().await?; let mut rows = sqlx::query("Select id, value from table where name = $1") .bind(name) .fetch(&mut connection); let mut results = Vec::new(); while let Some(row) = rows.try_next().await? { results.push(NamedValue { name, value: row.try_get("value")? }) } Ok(results) } }
修改后编译器可正确推断生命周期,代码编译通过。
内容的提问来源于stack exchange,提问作者tamathews01
相关产品推荐
相关产品推荐

