Search code examples
rustobserver-pattern

How do I convert a concrete, statically dispatched `T: Trait` to a dynamically dispatched `dyn Trait`?


In this code, I have the skeleton of an observable system. The documentation on implementing Observable and other decoupling patterns are usually missing a final step on allowing access to the listener while also having it be &mut during the notification call, but that's what RefCell is intended to handle. So I have this code all worked out, but I'm having a last piece of trouble getting my concrete T into the &dyn Trait.

use std::{cell::RefCell, rc::Rc};

pub trait Observer {
    fn notify(&mut self);
}

#[derive(Default)]
pub struct Subject<'s> {
    listeners: Vec<Rc<RefCell<Box<dyn Observer + 's>>>>,
}

pub fn wrap<T>(t: T) -> Rc<RefCell<Box<T>>> {
    Rc::new(RefCell::new(Box::new(t)))
}

impl<'s> Subject<'s> {
    pub fn add_observer(&mut self, observer: Rc<RefCell<Box<dyn Observer + 's>>>) {
        self.listeners.push(observer)
    }

    pub fn notify(&mut self) {
        for listener in &mut self.listeners {
            listener.borrow_mut().notify();
        }
    }
}

#[cfg(test)]
mod test {
    use super::{wrap, Observer, Subject};

    #[derive(Default)]
    pub struct Counter {
        count: usize,
    }

    impl Observer for Counter {
        fn notify(&mut self) {
            self.count += 1;
        }
    }

    #[test]
    fn it_observes() {
        let mut subject = Subject::default();
        let counter = wrap(Counter::default());

        subject.add_observer(counter); // mismatched types

        for i in 1..5 {
            subject.notify();
            subject.notify();

            assert_eq!(counter.borrow().count, i * 2);
        }
    }
}

The full error is

error[E0308]: mismatched types
  --> src/observer.rs:48:30
   |
48 |         subject.add_observer(counter);
   |                              ^^^^^^^ expected trait object `dyn Observer`, found struct `Counter`
   |
   = note: expected struct `Rc<RefCell<Box<(dyn Observer + 'static)>>>`
              found struct `Rc<RefCell<Box<Counter>>>

I've seen (what looks like) this pattern used in a number of contexts, but I can't tell what either I'm missing or doing differently.

How do I get the T: Trait out of its static dispatch and into dynamic dispatch?


Solution

  • You can take to boxing outside of your wrap function, and box the counter itself with the proper box type:

    
    pub fn wrap<T>(t: T) -> Rc<RefCell<T>> {
        Rc::new(RefCell::new(t))
    }
    ...
    let counter: Box<dyn Observer> = Box::new(Counter::default());
    let counter = wrap(counter);
    

    Playground

    Btw, once you have the dynamic dispatch, you have an dyn Observer so you lose access to the Counter itself. You will have to take ownership of it and downcast the pointer to the concrete type again.