利用Erased-trait进行类型擦除
为什么要类型擦除
这里的类型擦除和通常我们听到的,在 Java 里的类型擦除不太一样,这里的类型擦除是指,通过某些手段,屏蔽掉一些我们不关心的范型,从而达到统一存储的目的,这样说可能很抽象,直接看代码
pub trait Source{
type Event
pub fn poll(&self)->Result<Self::Event,SourceError>
}
pub struct SourceEntry<S:Source,T>{
source:S,
callback: Box<dyn Fn(S::Event,T)>
}
pub struct SourceList<S:Source,T>{
sources:Vec<Rc<RefCell<SourceEntry<S,T>>>>
}我想实现一个事件源的队列,然后每次 poll 对应的事件,如果 poll 方法返回Ok(event),就使用对应的回调进行处理,接收事件本身带来的event和从外部传入的参数T,这看起来没什么问题,但是,如果我要存储多个类型的事件源(或者有不同 event 的同种源,例如type Event = ()和type Event = Instant这两种Timeout事件源,实际上是不同类型的 Source),因为Vec<>只能存储相同类型的对象,因此就没办法通过一个SourceList存储了,而从上层的视角来看,我其实并不关心底层的Source是什么类型,我只是想要知道poll的结果,然后传一个T类型的参数给回调闭包即可,所以我们就需要通过一些方法,把S类型擦除掉,对上层只保留T范型。
抽象层
All problems in computer science can be solved by another level of indirection
—— Butler Lampson
我们现在知道,不管怎样,总会有一个中间层需要同时处理底层poll返回的event和上层传来的T,然后视结果对 callback 进行调用,我们不妨先记为
pub trait SourceDispatcher<T> {
fn poll(&mut self, value: &mut T) -> Result<(), SourceError>;
}同时,因为我们希望S和T只会同时出现在这一层,也就是把S隔离在底层,T隔离在上层,因此 callback 应该设计成一个没有类型的形式,再在中间层进行约束
pub struct SourceEntry<S,F>{
source:S,
callback: F
}并实现SourceDispatcher
impl<S, T, F> SourceDispatcher<T> for RefCell<SourceEntry<S, F>>
where
S: Source,
F: FnMut(S::Event, &mut T),
{
fn poll(&mut self, value: &mut T) -> Result<(), SourceError> {
let mut entry = self.borrow_mut();
let r = entry.source.poll();
if let Ok(msg) = r {
(entry.callback)(msg, value);
return Ok(());
} else {
return r.unwrap_err();
}
}
}那么我只要实现从RefCell<SourceEntry<S, F>>到dyn SourceDispatcher<T>到转换就可以了,这里就引入了erased trait
Erased Trait
trait ErasedDispatcher<'a, S, T> {
fn as_source_ref(&self) -> Ref<S>;
fn as_source_mut(&self) -> RefMut<S>;
fn into_source_inner(self: Rc<Self>) -> S;
fn into_event_dispatcher(self: Rc<Self>) -> Rc<dyn SourceDispatcher<T> + 'a>;
}
impl<'a, S, T, F> ErasedDispatcher<'a, S, T> for RefCell<SourceEntry<S, F>>
where
S: Source + 'a,
F: FnMut(S::Event, &mut T) + 'a,
{
fn as_source_ref(&self) -> Ref<S> {
return Ref::map(self.borrow(), |inner| &inner.source);
}
fn as_source_mut(&self) -> RefMut<S> {
return RefMut::map(self.borrow_mut(), |inner| &mut inner.source);
}
fn into_source_inner(self: Rc<Self>) -> S {
if let Ok(ref_cell) = Rc::try_unwrap(self) {
ref_cell.into_inner().source
} else {
panic!("Dispatcher is borrowed");
}
}
fn into_event_dispatcher(self: Rc<Self>) -> Rc<dyn SourceDispatcher<T> + 'a> {
self as Rc<dyn SourceDispatcher<T> + 'a>
}
}至此,source_list就可以被转换成,屏蔽掉了S类型
pub struct SourceList<T>{
sources: Vec<Box<dyn SourceDispatcher<T>>>
}