Rust中SaleorApp的APL字段:泛型还是Box<dyn APL>实现动态调用?
解决Rust中SaleorApp动态选择APL实现的问题
针对你的需求——运行时根据环境变量选择不同APL实现,同时支持直接调用saleor_app.apl.get(...)的逻辑,下面提供两种可行的解决方案:
方案一:调整APL trait以支持Trait对象(推荐,扩展性好)
你原有的APL trait因使用impl Future作为返回值,无法直接转化为trait对象——Rust的trait对象要求方法返回具体、可确定大小的类型,而impl Future是匿名类型,无法满足该约束。
解决方法是借助async-trait宏将异步方法的返回值统一为Box<dyn Future>,同时移除trait的Sized约束(trait对象不需要Sized):
1. 修改APL trait定义
use async_trait::async_trait; use std::error::Error; // 示例AuthData类型 #[derive(Debug, Clone)] pub struct AuthData { /* 自定义字段 */ } // 移除Sized约束,添加async_trait宏适配异步trait对象 #[async_trait] pub trait APL: Send + Sync + Clone + std::fmt::Debug { async fn get(&self, saleor_api_url: &str) -> Result<AuthData, Box<dyn Error>>; async fn set(&self, auth_data: AuthData) -> Result<(), Box<dyn Error>>; async fn delete(&self, saleor_api_url: &str) -> Result<(), Box<dyn Error>>; }
2. 定义SaleorApp结构体
使用Box<dyn APL>作为apl字段类型,实现动态多态:
pub struct SaleorApp { pub apl: Box<dyn APL>, }
3. 实现create_app函数
现在可以根据配置动态选择APL实现并装箱:
// 假设你的Config和AplType定义 pub enum AplType { Env, File, Redis } pub struct Config { pub apl: AplType, pub apl_url: String, pub app_api_base_url: String, } // 假设各个APL实现已定义 #[derive(Debug, Clone)] pub struct EnvApl; #[derive(Debug, Clone)] pub struct FileApl { pub path: String } #[derive(Debug, Clone)] pub struct RedisApl; impl RedisApl { pub fn new(_url: String, _base_url: String) -> anyhow::Result<Self> { Ok(Self) } } // 为EnvApl实现APL trait(其他APL实现类似) #[async_trait] impl APL for EnvApl { async fn get(&self, _: &str) -> Result<AuthData, Box<dyn Error>> { Ok(AuthData {}) } async fn set(&self, _: AuthData) -> Result<(), Box<dyn Error>> { Ok(()) } async fn delete(&self, _: &str) -> Result<(), Box<dyn Error>> { Ok(()) } } pub fn create_app(config: Config) -> anyhow::Result<SaleorApp> { use AplType::{Env, File, Redis}; let apl: Box<dyn APL> = match config.apl { Env => Box::new(EnvApl), File => Box::new(FileApl { path: "apls.txt".to_string() }), Redis => Box::new(RedisApl::new(config.apl_url, config.app_api_base_url)?), }; Ok(SaleorApp { apl }) }
调用示例
#[tokio::main] async fn main() -> anyhow::Result<()> { let config = Config { apl: AplType::Redis, apl_url: "redis://localhost:6379".to_string(), app_api_base_url: "http://localhost:3000".to_string(), }; let app = create_app(config)?; let auth_data = app.apl.get("10.1:3000/gql/").await?; Ok(()) }
该方案扩展性强,新增APL实现只需实现APL trait即可,无需修改其他代码。虽然存在少量堆分配,但这是Rust处理异步动态多态的标准方式,性能影响可忽略。
方案二:用枚举封装所有APL实现(无堆分配,性能最优)
如果你的APL类型固定且数量不多,可以用枚举封装所有实现,完全避免堆分配,实现静态分发:
1. 定义APL枚举
pub enum AplImpl { Env(EnvApl), File(FileApl), Redis(RedisApl), }
2. 为枚举实现APL trait
#[async_trait] impl APL for AplImpl { async fn get(&self, saleor_api_url: &str) -> Result<AuthData, Box<dyn Error>> { match self { AplImpl::Env(apl) => apl.get(saleor_api_url).await, AplImpl::File(apl) => apl.get(saleor_api_url).await, AplImpl::Redis(apl) => apl.get(saleor_api_url).await, } } async fn set(&self, auth_data: AuthData) -> Result<(), Box<dyn Error>> { match self { AplImpl::Env(apl) => apl.set(auth_data).await, AplImpl::File(apl) => apl.set(auth_data).await, AplImpl::Redis(apl) => apl.set(auth_data).await, } } async fn delete(&self, saleor_api_url: &str) -> Result<(), Box<dyn Error>> { match self { AplImpl::Env(apl) => apl.delete(saleor_api_url).await, AplImpl::File(apl) => apl.delete(saleor_api_url).await, AplImpl::Redis(apl) => apl.delete(saleor_api_url).await, } } }
3. 定义SaleorApp并实现create_app
pub struct SaleorApp { pub apl: AplImpl, } pub fn create_app(config: Config) -> anyhow::Result<SaleorApp> { use AplType::{Env, File, Redis}; let apl = match config.apl { Env => AplImpl::Env(EnvApl), File => AplImpl::File(FileApl { path: "apls.txt".to_string() }), Redis => AplImpl::Redis(RedisApl::new(config.apl_url, config.app_api_base_url)?), }; Ok(SaleorApp { apl }) }
该方案无堆分配,性能更好,但缺点是每次新增APL类型都需要修改枚举和对应的trait实现,扩展性稍差。
原泛型方案不可行的原因
你原有的泛型SaleorApp<A: APL>要求编译时确定A的类型,但create_app是根据运行时配置选择不同APL类型,编译器无法在编译时确定A的具体类型,因此会出现类型不兼容错误。泛型方案仅适用于调用者提前知道APL类型的场景,不适合动态选择的需求。
内容的提问来源于stack exchange,提问作者djkato
相关产品推荐
相关产品推荐

