Rust中如何设计Trait使实现者支持Into<Self::Coord>类型索引?
在Rust 1.66.0中,定义了如下Trait Foo,用于描述容纳Elem类型元素、可通过Coord类型索引的容器:
pub trait Foo: Index<Self::Coord> { type Elem; type Coord; }
现希望实现该Trait的类型支持所有可转换为Self::Coord(即实现Into<Self::Coord>)的类型进行索引,尝试修改Trait定义为:
pub trait Foo: Index<dyn Into<Self::Coord>> { type Elem; type Coord; }
但编译器报错:
the trait
Intocannot be made into an object
the trait cannot be made into an object because it requiresSelf: Sized
for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit ...
为什么原方案失败
Into trait带有Self: Sized的隐式约束(因它继承自From,而From的方法要求Self是Sized类型),而动态trait对象(dyn Into<...>)属于无大小类型(unsized),违反了该约束,因此无法作为Index的索引类型使用。
正确实现方式
方式1:提供自定义索引方法(推荐,无孤儿规则限制)
保持原Foo trait的基础定义,新增一个默认实现的泛型方法来处理可转换的索引类型:
use std::ops::Index; pub trait Foo: Index<Self::Coord> { type Elem; type Coord; // 接受所有可转换为Coord的类型作为索引 fn get(&self, idx: impl Into<Self::Coord>) -> &Self::Elem { &self[idx.into()] } }
实现Foo的类型会自动拥有get方法,示例:
struct MyContainer { data: Vec<i32>, } impl Foo for MyContainer { type Elem = i32; type Coord = usize; } impl Index<usize> for MyContainer { type Output = i32; fn index(&self, idx: usize) -> &Self::Output { &self.data[idx] } } fn main() { let container = MyContainer { data: vec![1,2,3] }; // 可以用u32(可转换为usize)作为索引 println!("{}", container.get(2u32)); // 输出3 }
方式2:支持[]操作符(需处理孤儿规则)
如果想保留[]索引语法,需要为每个实现Foo的类型手动实现Index<Idx>,或用宏自动生成实现:
首先定义宏:
use std::ops::Index; pub trait Foo: Index<Self::Coord> { type Elem; type Coord; } // 自动为实现Foo的类型生成Index<Idx>的实现 macro_rules! impl_index_for_into { ($container:ty, $coord:ty) => { impl<Idx: Into<$coord>> Index<Idx> for $container { type Output = <$container as Foo>::Elem; fn index(&self, idx: Idx) -> &Self::Output { &self[idx.into()] } } }; }
然后为你的容器类型调用宏:
struct MyContainer { data: Vec<i32>, } impl Foo for MyContainer { type Elem = i32; type Coord = usize; } // 实现基础的Index<usize> impl Index<usize> for MyContainer { type Output = i32; fn index(&self, idx: usize) -> &Self::Output { &self.data[idx] } } // 调用宏生成所有Into<usize>类型的Index实现 impl_index_for_into!(MyContainer, usize); fn main() { let container = MyContainer { data: vec![1,2,3] }; // 直接用u32作为索引 println!("{}", container[2u32]); // 输出3 }
注意:此方式受Rust孤儿规则限制,只能为你自己 crate 中定义的类型生成实现,无法为标准库或第三方 crate 的类型添加此实现。
内容的提问来源于stack exchange,提问作者Slartibartfast

