Rust动态切换仓库时Trait向上转换报错的解决方法咨询
我创建了一个单文件项目,尝试根据运行时变量切换使用的仓库,出现了 trait 向上强制转换的错误。错误信息如下:
error[E0658]: cannot cast
dyn RepoSuperTraittodyn CommandsRepoTrait, trait upcasting coercion is experimental
--> src\main.rs:376:24
|
376 | commands_repo: db_repo.clone(),
| ^^^^^^^^^^^^^^^
|
= note: see issue #65991 for more information
= note: required when coercingArc<dyn RepoSuperTrait>intoArc<(dyn CommandsRepoTrait + 'static)>
我想采用论坛给出的两个建议之一来修复:
- 提取所需功能到对象安全的 trait 中并使用 Box
- 提取所需功能到对象安全的 trait 中并使用栈上动态分发模式(本质和前者类似,但堆分配更少,代码稍多)
相关代码片段:
trait RepoSuperTrait: services::commands::CommandsRepoTrait + services::queries::QueriesRepoTrait {} impl<T: services::commands::CommandsRepoTrait + services::queries::QueriesRepoTrait> RepoSuperTrait for T {} pub struct Deps { pub commands_repo: Arc<dyn services::commands::CommandsRepoTrait>, pub queries_repo: Arc<dyn services::queries::QueriesRepoTrait>, } let use_postgres = false; let db_repo: Arc<dyn RepoSuperTrait + 'static> = if use_postgres { let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); Arc::new(repositories::postgres::Repo::new(pg_pool)) } else { Arc::new(repositories::inmemory::Repo::new()) }; let deps = Arc::new(Deps { commands_repo: db_repo.clone(), queries_repo: db_repo, });
问题出在哪
你遇到的错误是因为Rust 默认不支持 trait 对象的向上强制转换(也就是把 dyn 父trait 转换成它继承的 dyn 子trait),这个特性目前还是实验性的,所以直接把 Arc<dyn RepoSuperTrait> 赋值给 Arc<dyn CommandsRepoTrait> 会报错。
你的 RepoSuperTrait 是继承了两个子 trait 的组合 trait,但当你把具体类型包装成 Arc<dyn RepoSuperTrait> 后,Rust 无法自动将其向下转换为子 trait 的对象。
按照建议的修复方法
方法1:提取功能到目标 trait 并直接生成对应 Arc 对象
既然 Deps 需要分别持有命令和查询仓库的 trait 对象,我们可以直接在创建仓库实例时,就分别生成两个子 trait 的 Arc 对象,绕开组合 trait 的转换问题:
pub struct Deps { pub commands_repo: Arc<dyn services::commands::CommandsRepoTrait>, pub queries_repo: Arc<dyn services::queries::QueriesRepoTrait>, } let use_postgres = false; let (commands_repo, queries_repo) = if use_postgres { let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); let repo = Arc::new(repositories::postgres::Repo::new(pg_pool)); ( repo.clone() as Arc<dyn services::commands::CommandsRepoTrait>, repo as Arc<dyn services::queries::QueriesRepoTrait> ) } else { let repo = Arc::new(repositories::inmemory::Repo::new()); ( repo.clone() as Arc<dyn services::commands::CommandsRepoTrait>, repo as Arc<dyn services::queries::QueriesRepoTrait> ) }; let deps = Arc::new(Deps { commands_repo, queries_repo, });
这种方式直接从具体类型转换成需要的子 trait 对象,完全符合 Rust 的类型系统规则,实现简单且适配你现有的 Deps 结构。
方法2:使用栈上动态分发(减少堆分配)
如果想避免额外的 Arc 克隆或堆分配,可以用枚举封装两种仓库类型,然后为枚举手动实现对应的子 trait,所有分发逻辑在栈上完成:
// 用枚举封装两种仓库类型 enum Repo { Postgres(repositories::postgres::Repo), InMemory(repositories::inmemory::Repo), } // 为枚举实现 CommandsRepoTrait,转发调用到内部具体类型 impl services::commands::CommandsRepoTrait for Repo { fn your_command_method(&self, args: /* 方法参数 */) -> /* 返回值 */ { match self { Repo::Postgres(repo) => repo.your_command_method(args), Repo::InMemory(repo) => repo.your_command_method(args), } } // 实现 CommandsRepoTrait 的其他方法... } // 同样实现 QueriesRepoTrait impl services::queries::QueriesRepoTrait for Repo { fn your_query_method(&self, args: /* 方法参数 */) -> /* 返回值 */ { match self { Repo::Postgres(repo) => repo.your_query_method(args), Repo::InMemory(repo) => repo.your_query_method(args), } } // 实现 QueriesRepoTrait 的其他方法... } // 创建 Deps let use_postgres = false; let repo = if use_postgres { let pg_pool = sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap(); Repo::Postgres(repositories::postgres::Repo::new(pg_pool)) } else { Repo::InMemory(repositories::inmemory::Repo::new()) }; // 按需用 Arc 包裹,或直接使用栈上枚举(无需共享所有权时) let deps = Arc::new(Deps { commands_repo: Arc::new(repo.clone()), queries_repo: Arc::new(repo), });
这种方式不需要 trait 对象的强制转换,堆分配更少,但需要为枚举手动实现所有子 trait 的方法,代码量稍多。
总结
核心问题是试图将组合 trait 的对象转换为其子 trait 对象,而 Rust 目前不支持这种隐式转换。上面两种方法都能解决问题:第一种简单直接,适配现有结构;第二种性能更优,但需要额外实现代码。
内容的提问来源于stack exchange,提问作者Fred Hors

