Rust使用dyn Domainable报trait无法构造对象错误如何解决
问题背景
期望实现一套约束满足问题相关的类型结构,核心代码骨架初始设计如下:
use std::collections::HashSet; use std::hash::Hash; pub trait Domainable: Eq + Hash {} impl<T: Eq + Hash> Domainable for T {} pub struct Variable<T: Domainable> { domain: HashSet<T>, } pub struct Assignment<'a, T: Domainable> { variable: &'a Variable<T>, assignment: T, } pub struct State<'a, T: Domainable> { assignments: Vec<&'a Assignment<'a, dyn Domainable>>, }
核心需求是在State结构体内存储一个Assignment向量,向量内的元素可对应不同的、均实现Domainable trait的类型,使用示例如下:
let var1 = Variable::new(HashSet::from([1, 2, 3, 4])); let var2 = Variable::new(HashSet::from([true, false])); let assignment1 = Assignment::new(&var1, 2); let assignment2 = Assignment::new(&var2, false); let state = State::new(vec![assignment1, assignment2]);
编写过程中dyn Domainable持续抛出编译错误:
error[E0038]: the trait `Domainable` cannot be made into an object --> src/main.rs:17:41 | 17 | assignments: Vec<&'a Assignment<'a, dyn Domainable>>, | ^^^^^^^^^^^^^^ `Domainable` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically | ::: src/main.rs:4:11 | 4 | pub trait Domainable: Eq + Hash {} | ---------- this trait cannot be made into an object...
尝试按照异构集合资料使用&dyn Trait写法仍无法通过编译。
问题根因
编译错误核心是**Domainable trait不满足对象安全要求**,无法被构造为dyn Domainable trait对象:
- 你的
Domainable继承了Eq和Hash两个trait,其中Eq依赖的PartialEq定义了fn eq(&self, other: &Self) -> bool方法,这类方法签名中出现具体Self类型、依赖Self: Sized约束的trait,无法生成动态分发需要的vtable,自然不支持trait对象。 - 额外逻辑错误:
Assignment<'a, i32>和Assignment<'a, bool>是完全不同的具体类型,就算Domainable满足对象安全,你也无法把这两个不同类型的引用直接强转为&Assignment<'a, dyn Domainable>,二者内存布局不兼容。
可行实现方案
不要尝试把内部的Domainable泛型参数直接擦除为trait对象,而是给Assignment本身定义一套对象安全的公共操作trait,把不同类型的Assignment统一擦除为这个trait的对象存入Vec即可,参考实现如下:
use std::collections::HashSet; use std::hash::Hash; use std::any::Any; // 保留原Domainable定义,不需要修改 pub trait Domainable: Eq + Hash {} impl<T: Eq + Hash> Domainable for T {} pub struct Variable<T: Domainable> { domain: HashSet<T>, } impl<T: Domainable> Variable<T> { pub fn new(domain: HashSet<T>) -> Self { Self { domain } } } pub struct Assignment<'a, T: Domainable> { variable: &'a Variable<T>, assignment: T, } impl<'a, T: Domainable> Assignment<'a, T> { pub fn new(variable: &'a Variable<T>, assignment: T) -> Self { // 可按需添加赋值合法性校验 debug_assert!(variable.domain.contains(&assignment)); Self { variable, assignment } } } // 定义对象安全的Assignment公共trait,所有需要对Assignment做的操作都在这里声明 pub trait AnyAssignment { // 如需运行时按类型获取内部赋值,可通过Any做安全类型转换 fn as_any(&self) -> &dyn Any; } // 为所有符合要求的Assignment实现该trait impl<'a, T: Domainable + 'static> AnyAssignment for Assignment<'a, T> { fn as_any(&self) -> &dyn Any { self } } pub struct State<'a> { // 存储擦除类型后的Assignment trait对象 assignments: Vec<&'a dyn AnyAssignment>, } impl<'a> State<'a> { pub fn new(assignments: Vec<&'a dyn AnyAssignment>) -> Self { Self { assignments } } // 示例:按类型获取对应变量的赋值 pub fn get_assignment<T: Domainable + 'static>(&self) -> Option<&T> { for assn in &self.assignments { if let Some(assn) = assn.as_any().downcast_ref::<Assignment<T>>() { return Some(&assn.assignment); } } None } } fn main() { let var1 = Variable::new(HashSet::from([1, 2, 3, 4])); let var2 = Variable::new(HashSet::from([true, false])); let assignment1 = Assignment::new(&var1, 2); let assignment2 = Assignment::new(&var2, false); let state = State::new(vec![&assignment1, &assignment2]); // 功能验证 assert_eq!(state.get_assignment::<i32>(), Some(&2)); assert_eq!(state.get_assignment::<bool>(), Some(&false)); }
如果不需要运行时按类型取具体赋值,可以去掉
Any相关逻辑,直接在AnyAssignmenttrait里定义无泛型的业务方法即可,运行时性能会更好。如果需要State持有Assignment的所有权而非借用,只需要把State内的字段类型改为Vec<Box<dyn AnyAssignment>>,创建时传入vec![Box::new(assignment1), Box::new(assignment2)]即可,逻辑和借用版本完全一致。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

