如何在不使用Phantom Data的情况下将泛型类型用作关联类型?
我定义了一个带有关联类型的 trait:
pub trait View: Default { type State; fn draw(&mut self, state: &mut Self::State); }
还有一个适合作为State关联类型的泛型结构体:
pub struct MenuState<I> { pub items: Vec<I>, }
以下代码在 Rust 1.66 中无法编译,报错原因是I未被约束:
#[derive(Default)] pub struct MenuView { // fields here } impl<I: ToString + Copy> View for MenuView { type State = MenuState<I>; fn draw(&mut self, _state: &mut Self::State) { } }
注:ToString和Copy约束是MenuView实现View时所需的。
我知道可以通过添加PhantomData让MenuView变成泛型类型来解决这个问题,但这种方式不够简洁。我觉得上面的代码应该只需要一些语法调整就能修复,想请教:
是否存在无需修改View trait、也不使用PhantomData的简单解决方案?如果因为某些根本原因无法通过语法修正解决,原因是什么?
不存在无需泛型化MenuView或PhantomData的简单语法修正方案
根本原因在于 Rust 的 trait 实现规则:对于非泛型类型(比如这里的MenuView),其 trait 实现必须是唯一的。
你当前的代码试图为同一个非泛型类型MenuView实现View trait,但关联类型State依赖于一个未绑定的泛型参数I——这意味着理论上可以存在无数种不同的I对应无数种View实现,这直接违反了 Rust 对 trait 实现唯一性的要求。编译器无法确定当你使用MenuView作为View时,应该使用哪一种I对应的State类型。
为什么PhantomData是常规解决方案?
添加PhantomData<I>将MenuView变成泛型类型MenuView<I>后,每个不同的I都会对应一个独立的MenuView<I>类型,此时为每个MenuView<I>实现View就满足了唯一性要求——每个泛型实例对应唯一的 trait 实现,编译器可以明确区分。
替代方案:固定I的类型
如果你不需要MenuView支持任意I,可以直接在实现中固定I的具体类型,比如:
#[derive(Default)] pub struct MenuView { // fields here } impl View for MenuView { type State = MenuState<String>; // 直接固定I为String fn draw(&mut self, _state: &mut Self::State) { } }
这种方式不需要泛型或PhantomData,但代价是MenuView只能配合固定类型的MenuState使用,失去了泛型灵活性。
内容的提问来源于stack exchange,提问作者Denys Séguret

