我想封装一个函数,把具体的数据库类型(如Sqlite,Any)替换为泛型后就会报错生命周期不满足,求教怎么解决?
use futures_core::future::BoxFuture;
use sqlx::{Any, Database, Executor, IntoArguments, Sqlite, query_with};
struct TestQuery<'a, DB: Database> {
sql: String,
arg: DB::Arguments<'a>,
}
//正常
impl<'a> TestQuery<'a, Sqlite> {
fn execute<'e, E>(
self,
e: E,
) -> BoxFuture<'e, Result<<Sqlite as Database>::QueryResult, sqlx_core::Error>>
where
'a: 'e,
E: Executor<'e, Database = Sqlite> + 'e,
{
Box::pin(async move {
let TestQuery { sql, arg } = self;
query_with(&sql, arg).execute(e).await
})
}
}
//正常
impl<'a> TestQuery<'a, Any> {
fn execute<'e, E>(
self,
e: E,
) -> BoxFuture<'e, Result<<Any as Database>::QueryResult, sqlx_core::Error>>
where
'a: 'e,
E: Executor<'e, Database = Any> + 'e,
{
Box::pin(async move {
let TestQuery { sql, arg } = self;
query_with(&sql, arg).execute(e).await
})
}
}
//把具体的数据库类型修改为泛型后就不行
impl<'a, DB: Database> TestQuery<'a, DB> {
fn execute1<'e, E>(self, e: E) -> BoxFuture<'e, Result<DB::QueryResult, sqlx_core::Error>>
where
'a: 'e,
E: Executor<'e, Database = DB> + 'e,
DB::Arguments<'a>: IntoArguments<'e, DB>,
{
//报错: lifetime may not live long enough 。 coercion requires that `'a` must outlive `'static`
Box::pin(async move {
let TestQuery { sql, arg } = self;
query_with(&sql, arg).execute(e).await
})
}
}
}
1
共 10 条评论, 1 页
评论区
写评论那就很复杂了,sql在日志里打印吧,不纠结了。谢谢!
--
👇
xiaoyaou: 个人理解,原因应该是底层
DB::Arguments<'a>
泛型的问题。因为泛型类型不知道具体的实际类型,编译器无法推导泛型的协变关系,也即没办法证明
DB::Arguments<'a>: DB::Arguments<'e>
生命周期可以协变,所以只要用了arg
参数,'a
也就被强制限制死了。所以哪怕是手动给&mut self和返回的Futue值声明一个新的生命周期'b,编译器也会要求'b: 'a
。因此只要泛型版的execute1
被调用,self就被永久可变借用了(self生命周期内)。而如果是在具体DB类型的impl里这么做,编译器能够根据具体类型自动进行协变转换,从而可以收缩必要的生命周期界限
个人理解,原因应该是底层
DB::Arguments<'a>
泛型的问题。因为泛型类型不知道具体的实际类型,编译器无法推导泛型的协变关系,也即没办法证明
DB::Arguments<'a>: DB::Arguments<'e>
生命周期可以协变,所以只要用了arg
参数,'a
也就被强制限制死了。所以哪怕是手动给&mut self和返回的Futue值声明一个新的生命周期'b,编译器也会要求'b: 'a
。因此只要泛型版的execute1
被调用,self就被永久可变借用了(self生命周期内)。而如果是在具体DB类型的impl里这么做,编译器能够根据具体类型自动进行协变转换,从而可以收缩必要的生命周期界限
--
👇
‘static: 我感觉也是,但是找不到原因
--
👇
xiaoyaou: 这个大概是因为execute1的签名约束吧:
execute1
的入参生命周期被绑定在'a
与self
持有的生命周期一致,导致编译器认为借用会持续到self
销毁我感觉也是,但是找不到原因
--
👇
xiaoyaou: 这个大概是因为execute1的签名约束吧:
execute1
的入参生命周期被绑定在'a
与self
持有的生命周期一致,导致编译器认为借用会持续到self
销毁这个大概是因为execute1的签名约束吧:
execute1
的入参生命周期被绑定在'a
与self
持有的生命周期一致,导致编译器认为借用会持续到self
销毁--
👇
‘static: 后面倒是找到了个解法,函数签名修改为&'a mut self,理论上execute之后&'a mut self的借用应该结束,不知道为啥编译器识别不了,无法获取其中的sql。原本想把它封装为一个构建器的,构建执行之后能获取执行的sql
后面倒是找到了个解法,函数签名修改为&'a mut self,理论上execute之后&'a mut self的借用应该结束,不知道为啥编译器识别不了,无法获取其中的sql。原本想把它封装为一个构建器的,构建执行之后能获取执行的sql
--
👇
xiaoyaou: 不知道你能不能接受使用
unsafe
:因为
move
捕获了所有权,按理说解构后的sql
和arg
都应该是能跟async块的存活周期关联的,但是不清楚为什么编译器就是推导不出&sql
可以和arg
拥有相同的生命周期'a
。--
👇
‘static: 是的,换成引用可以。我以前就是引用的版本,突然间看到其它项目用具体的db类型可以避免引用,所以想弄一个不引用的来简化下操作,目前来看搞不成了。
不知道你能不能接受使用
unsafe
:因为
move
捕获了所有权,按理说解构后的sql
和arg
都应该是能跟async块的存活周期关联的,但是不清楚为什么编译器就是推导不出&sql
可以和arg
拥有相同的生命周期'a
。--
👇
‘static: 是的,换成引用可以。我以前就是引用的版本,突然间看到其它项目用具体的db类型可以避免引用,所以想弄一个不引用的来简化下操作,目前来看搞不成了。
是的,换成引用可以。我以前就是引用的版本,突然间看到其它项目用具体的db类型可以避免引用,所以想弄一个不引用的来简化下操作,目前来看搞不成了。
--
👇
苦瓜小仔: 那你可以把 sql 字段变成引用
gist
写
sql: String
是不行的,因为 &sql 在 execute1 函数内创建,需要一种方式表达DB::Arguments<'a>
中的 'a 与&'q sql
中的 'q 具有'q: 'a
关系。这无法被 Rust 的 trait bound 表达。那你可以把 sql 字段变成引用
gist
写
sql: String
是不行的,因为 &sql 在 execute1 函数内创建,需要一种方式表达DB::Arguments<'a>
中的 'a 与&'q sql
中的 'q 具有'q: 'a
关系。这无法被 Rust 的 trait bound 表达。谢谢大佬!execute1的可以编译过,但是在调用时候,除了pgArguments,mysqlArguments这种不带生命周期的数据库参数,其他像 AnyArguments<'q> 这种带生命周期的就没法绑定参数和调用
--
👇
苦瓜小仔: 你可以这样写(没有 BoxFuture):
gist
你可以这样写(没有 BoxFuture):
gist