You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust闭包访问结构体RedisAuth的redis_pool遇逃逸错误求解决

问题

我们定义了一个用于存储Redis连接池的结构体:

struct RedisAuth {
    #[allow(dead_code)]
    configuration: Conf,
    redis_pool: r2d2_pool::R2D2Pool
}

现在需要实现一个包含闭包函数的Trait(必须遵循该实现模板):

impl Plugin for RedisAuth {
    type Config = Conf;

    async fn new(init: PluginInit<Self::Config>) -> Result<Self, BoxError> {
        tracing::info!("{}", init.config.message);
        let redis_pool = r2d2_pool::connect().unwrap();       
        Ok(RedisAuth { configuration: init.config, redis_pool })
    }

    fn supergraph_service(
        &self,
        service: supergraph::BoxService,
    ) -> supergraph::BoxService {
        
        ServiceBuilder::new()
                    .checkpoint(|request : supergraph::Request|  {
                        let operation_name = &request.supergraph_request.body().operation_name;
                        let pool = self.redis_pool;
                        // 在此处执行异步认证调用,判断是否继续请求
                        Ok(ControlFlow::Continue(request))
                    })
                    .buffered()
                    .service(service)
                    .boxed()
    }
}

在checkpoint闭包中访问self.redis_pool时,出现如下错误:

borrowed data escapes outside of associated function
self escapes the associated function body here.

需求:复用已初始化的连接池,不重复初始化,且无法修改闭包签名,求可行的解决方法或重构方案。

可行的解决方法

1. 用Arc包装连接池共享所有权

闭包需要长期持有连接池的访问权,但self是借用的,无法直接传递给闭包。将连接池放入Arc(原子引用计数智能指针),通过克隆Arc让闭包获取共享所有权,即可避免生命周期逃逸问题。

修改后的代码:

use std::sync::Arc;

struct RedisAuth {
    #[allow(dead_code)]
    configuration: Conf,
    redis_pool: Arc<r2d2_pool::R2D2Pool>
}

impl Plugin for RedisAuth {
    type Config = Conf;

    async fn new(init: PluginInit<Self::Config>) -> Result<Self, BoxError> {
        tracing::info!("{}", init.config.message);
        // 将连接池包装进Arc
        let redis_pool = Arc::new(r2d2_pool::connect().unwrap());       
        Ok(RedisAuth { configuration: init.config, redis_pool })
    }

    fn supergraph_service(
        &self,
        service: supergraph::BoxService,
    ) -> supergraph::BoxService {
        // 克隆Arc,闭包通过move获取该克隆实例
        let pool = Arc::clone(&self.redis_pool);
        
        ServiceBuilder::new()
                    .checkpoint(move |request : supergraph::Request|  {
                        let operation_name = &request.supergraph_request.body().operation_name;
                        // 使用克隆的Arc获取连接,执行认证逻辑
                        let conn = pool.get().unwrap();
                        // 异步认证逻辑...
                        Ok(ControlFlow::Continue(request))
                    })
                    .buffered()
                    .service(service)
                    .boxed()
    }
}

原理:Arc克隆仅增加引用计数,不会复制连接池本身,闭包持有克隆的Arc后,可安全地长期访问连接池,不会触发逃逸错误。

2. 全局静态单例连接池

如果整个应用只需要一个Redis连接池,可以将其声明为全局静态变量,在new方法中完成初始化,闭包直接访问全局实例。

使用once_cell实现线程安全的懒加载单例:

use once_cell::sync::Lazy;
use std::sync::Mutex;

// 全局静态连接池,首次访问时初始化
static REDIS_POOL: Lazy<Mutex<r2d2_pool::R2D2Pool>> = Lazy::new(|| {
    Mutex::new(r2d2_pool::connect().unwrap())
});

struct RedisAuth {
    #[allow(dead_code)]
    configuration: Conf
}

impl Plugin for RedisAuth {
    type Config = Conf;

    async fn new(init: PluginInit<Self::Config>) -> Result<Self, BoxError> {
        tracing::info!("{}", init.config.message);
        // 触发全局连接池初始化(首次调用时执行)
        let _ = REDIS_POOL.lock().unwrap();
        Ok(RedisAuth { configuration: init.config })
    }

    fn supergraph_service(
        &self,
        service: supergraph::BoxService,
    ) -> supergraph::BoxService {
        
        ServiceBuilder::new()
                    .checkpoint(|request : supergraph::Request|  {
                        let operation_name = &request.supergraph_request.body().operation_name;
                        // 获取全局连接池的锁,执行认证逻辑
                        let pool = REDIS_POOL.lock().unwrap();
                        let conn = pool.get().unwrap();
                        // 异步认证逻辑...
                        Ok(ControlFlow::Continue(request))
                    })
                    .buffered()
                    .service(service)
                    .boxed()
    }
}

注意:全局单例会增加代码耦合度,适合简单场景;若需要多实例连接池,该方法不适用。

3. 适配异步闭包的捕获方式

如果checkpoint支持异步闭包(从需求看需要执行异步认证),需配合Arc和async move闭包来传递连接池,确保异步逻辑的所有权安全:

use std::sync::Arc;

struct RedisAuth {
    #[allow(dead_code)]
    configuration: Conf,
    redis_pool: Arc<r2d2_pool::R2D2Pool>
}

impl Plugin for RedisAuth {
    type Config = Conf;

    async fn new(init: PluginInit<Self::Config>) -> Result<Self, BoxError> {
        tracing::info!("{}", init.config.message);
        let redis_pool = Arc::new(r2d2_pool::connect().unwrap());       
        Ok(RedisAuth { configuration: init.config, redis_pool })
    }

    fn supergraph_service(
        &self,
        service: supergraph::BoxService,
    ) -> supergraph::BoxService {
        let pool = Arc::clone(&self.redis_pool);
        
        ServiceBuilder::new()
                    .checkpoint(move |request : supergraph::Request| async move {
                        let operation_name = &request.supergraph_request.body().operation_name;
                        // 异步获取连接并执行认证逻辑
                        let conn = pool.get().await.unwrap();
                        // let is_valid = check_auth(&conn, operation_name).await;
                        // if !is_valid {
                        //     return Ok(ControlFlow::Break(Response::new(StatusCode::UNAUTHORIZED)));
                        // }
                        Ok(ControlFlow::Continue(request))
                    })
                    .buffered()
                    .service(service)
                    .boxed()
    }
}

说明:async move闭包会捕获变量的所有权,结合Arc克隆可安全地在异步逻辑中复用连接池,同时满足闭包签名要求。

内容的提问来源于stack exchange,提问作者GJoshi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:35:23