< 返回版块

‘static 发表于 2025-08-16 21:46

Tags:sqlx,生命周期

我想封装一个函数,把具体的数据库类型(如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
            })
        }
    }
}


评论区

写评论
作者 ‘static 2025-08-22 20:16

那就很复杂了,sql在日志里打印吧,不纠结了。谢谢!

--
👇
xiaoyaou: 个人理解,原因应该是底层DB::Arguments<'a>泛型的问题。

因为泛型类型不知道具体的实际类型,编译器无法推导泛型的协变关系,也即没办法证明DB::Arguments<'a>: DB::Arguments<'e>生命周期可以协变,所以只要用了arg参数,'a也就被强制限制死了。所以哪怕是手动给&mut self和返回的Futue值声明一个新的生命周期'b,编译器也会要求'b: 'a。因此只要泛型版的execute1被调用,self就被永久可变借用了(self生命周期内)。

而如果是在具体DB类型的impl里这么做,编译器能够根据具体类型自动进行协变转换,从而可以收缩必要的生命周期界限

xiaoyaou 2025-08-22 12:15

个人理解,原因应该是底层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的签名约束吧:

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'e, E>(&'a mut self, e: E) -> ...
}

execute1的入参生命周期被绑定在'aself持有的生命周期一致,导致编译器认为借用会持续到self销毁

作者 ‘static 2025-08-21 20:55

我感觉也是,但是找不到原因

--
👇
xiaoyaou: 这个大概是因为execute1的签名约束吧:

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'e, E>(&'a mut self, e: E) -> ...
}

execute1的入参生命周期被绑定在'aself持有的生命周期一致,导致编译器认为借用会持续到self销毁

xiaoyaou 2025-08-21 10:27

这个大概是因为execute1的签名约束吧:

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'e, E>(&'a mut self, e: E) -> ...
}

execute1的入参生命周期被绑定在'aself持有的生命周期一致,导致编译器认为借用会持续到self销毁

--
👇
‘static: 后面倒是找到了个解法,函数签名修改为&'a mut self,理论上execute之后&'a mut self的借用应该结束,不知道为啥编译器识别不了,无法获取其中的sql。原本想把它封装为一个构建器的,构建执行之后能获取执行的sql

作者 ‘static 2025-08-20 20:05

后面倒是找到了个解法,函数签名修改为&'a mut self,理论上execute之后&'a mut self的借用应该结束,不知道为啥编译器识别不了,无法获取其中的sql。原本想把它封装为一个构建器的,构建执行之后能获取执行的sql

 use sqlx::{Database, Error, Executor, IntoArguments, any::AnyArguments, query_with};

    struct TestQuery<'a, DB: Database> {
        sql: String,
        arg: Option<DB::Arguments<'a>>,
    }

    impl<'a, DB: Database> TestQuery<'a, DB> {
        async fn execute1<'e, E>(&'a mut self, e: E) -> Result<DB::QueryResult, Error>
        where
            E: Executor<'e, Database = DB> + 'e,
            DB::Arguments<'a>: IntoArguments<'a, DB>,
        {
            query_with(&self.sql, self.arg.take().unwrap())
                .execute(e)
                .await
        }

        fn sql(&self) -> &String {
            &self.sql
        }
    }

    #[tokio::test]
    async fn test_execute() {
        use sqlx::Arguments;
       
        let sql = "delete from t_user where user_id=$1".to_string();
        let mut args = AnyArguments::default();

        let pool = sqlx::AnyPool::connect("postgres://postgres:postgres@localhost/my_web")
            .await
            .unwrap();
        
        let _ = args.add(&id);

        let mut query = TestQuery {
            sql,
            arg: Some(args),
        };
        //sql的某些部分在执行时拼接
        query.execute1(&pool).await;
        //想在这里打印下sql
        //cannot borrow `query` as immutable because it is also borrowed as mutable mutable borrow later used
        println!("{}", query.sql());
    }

--
👇
xiaoyaou: 不知道你能不能接受使用unsafe:

unsafe { std::mem::transmute::<&str, &'a str>(&sql) // 手动帮助编译器确认&sql在'a生命周期内是有效的

因为move捕获了所有权,按理说解构后的sqlarg都应该是能跟async块的存活周期关联的,但是不清楚为什么编译器就是推导不出&sql可以和arg拥有相同的生命周期'a

--
👇
‘static: 是的,换成引用可以。我以前就是引用的版本,突然间看到其它项目用具体的db类型可以避免引用,所以想弄一个不引用的来简化下操作,目前来看搞不成了。

xiaoyaou 2025-08-20 17:06

不知道你能不能接受使用unsafe:

unsafe { std::mem::transmute::<&str, &'a str>(&sql) // 手动帮助编译器确认&sql在'a生命周期内是有效的

因为move捕获了所有权,按理说解构后的sqlarg都应该是能跟async块的存活周期关联的,但是不清楚为什么编译器就是推导不出&sql可以和arg拥有相同的生命周期'a

--
👇
‘static: 是的,换成引用可以。我以前就是引用的版本,突然间看到其它项目用具体的db类型可以避免引用,所以想弄一个不引用的来简化下操作,目前来看搞不成了。

作者 ‘static 2025-08-19 17:05

是的,换成引用可以。我以前就是引用的版本,突然间看到其它项目用具体的db类型可以避免引用,所以想弄一个不引用的来简化下操作,目前来看搞不成了。

--
👇
苦瓜小仔: 那你可以把 sql 字段变成引用

struct TestQuery<'a, DB: Database> {
    sql: &'a str,
    arg: DB::Arguments<'a>,
}

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'e, E>(self, e: E) -> Result<DB::QueryResult, sqlx_core::Error>
    where
        E: Executor<'e, Database = DB>,
        DB::Arguments<'a>: 'a + IntoArguments<'a, DB>,

gist

sql: String 是不行的,因为 &sql 在 execute1 函数内创建,需要一种方式表达 DB::Arguments<'a> 中的 'a 与 &'q sql 中的 'q 具有 'q: 'a 关系。这无法被 Rust 的 trait bound 表达。

苦瓜小仔 2025-08-19 09:42

那你可以把 sql 字段变成引用

struct TestQuery<'a, DB: Database> {
    sql: &'a str,
    arg: DB::Arguments<'a>,
}

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'e, E>(self, e: E) -> Result<DB::QueryResult, sqlx_core::Error>
    where
        E: Executor<'e, Database = DB>,
        DB::Arguments<'a>: 'a + IntoArguments<'a, DB>,

gist

sql: String 是不行的,因为 &sql 在 execute1 函数内创建,需要一种方式表达 DB::Arguments<'a> 中的 'a 与 &'q sql 中的 'q 具有 'q: 'a 关系。这无法被 Rust 的 trait bound 表达。

作者 ‘static 2025-08-17 15:32

谢谢大佬!execute1的可以编译过,但是在调用时候,除了pgArguments,mysqlArguments这种不带生命周期的数据库参数,其他像 AnyArguments<'q> 这种带生命周期的就没法绑定参数和调用

    #[tokio::test]
    async fn test_execute() {
        use sqlx::Arguments;
        // Your test code here let id = "9999_i64".to_string();
        let id = "9999_i64".to_string();
        let sql = "delete from t_user where user_id=$1".to_string();
        let mut args = AnyArguments::default();

        let pool = AnyPool::connect("postgres://postgres:postgres@localhost/my_web")
            .await
            .unwrap();
        //`id` does not live long enough
        args.add(&id);
        //implementation of `sqlx::IntoArguments` is not general enough......
        let query: TestQuery<'_, Any> = TestQuery { sql, arg: args };
        let res = query.execute1(&pool).await.unwrap();
        println!("{:?}", res.rows_affected());
    }

--
👇
苦瓜小仔: 你可以这样写(没有 BoxFuture):

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'c, E>(self, e: E) -> Result<DB::QueryResult, sqlx_core::Error>
    where
        E: Executor<'c, Database = DB>,
        for<'q> DB::Arguments<'a>: 'q + IntoArguments<'q, DB>,
    {
        let TestQuery { sql, arg } = self;
        query_with(&sql, arg).execute(e).await
    }
}

gist

苦瓜小仔 2025-08-17 08:55

你可以这样写(没有 BoxFuture):

impl<'a, DB: Database> TestQuery<'a, DB> {
    async fn execute1<'c, E>(self, e: E) -> Result<DB::QueryResult, sqlx_core::Error>
    where
        E: Executor<'c, Database = DB>,
        for<'q> DB::Arguments<'a>: 'q + IntoArguments<'q, DB>,
    {
        let TestQuery { sql, arg } = self;
        query_with(&sql, arg).execute(e).await
    }
}

gist

1 共 10 条评论, 1 页