跳至主要內容

利用Erased-trait进行类型擦除

Chiichen大约 3 分钟笔记RustRust

为什么要类型擦除

这里的类型擦除和通常我们听到的,在 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>;
}

同时,因为我们希望ST只会同时出现在这一层,也就是把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>>>
}