Reputation: 630
I am quite new to Rust, coming from a Java background. I am trying to implement the observer pattern in Rust (I guess this is not idiomatic Rust, though). My attempt is like this:
use crate::observable::{Listener, trigger_listeners};
mod observable {
pub trait Listener {
fn trigger(&mut self);
}
pub fn trigger_listeners(listeners: Vec<&mut Box<dyn Listener>>) {
for mut x in listeners {
x.trigger();
}
}
}
struct Mock {
times_called: u32
}
impl Listener for Mock {
fn trigger(&mut self) {
self.times_called += 1;
}
}
#[test]
fn test() {
let mut mock = Box::new(Mock{ times_called: 0 });
trigger_listeners(vec![&mut mock]);
assert_eq!(mock.times_called, 1)
}
Every listener should implement the Listener trait and be passed as an array to the function trigger_listener
.
This gives me the following error:
error[E0308]: mismatched types
--> src/lib.rs:27:28
|
27 | trigger_listeners(vec![&mut mock]);
| ^^^^^^^^^ expected trait observable::Listener, found struct `Mock`
|
= note: expected type `&mut std::boxed::Box<(dyn observable::Listener + 'static)>`
found type `&mut std::boxed::Box<Mock>`
error: aborting due to previous error
I was thinking that because the Mock implements the trait Listener, I can pass it as a reference.
My other try is to only use a Box which is moved (pub fn trigger_listeners(listeners: Vec<Box<dyn Listener>>) {}
). This works, but then I cannot access the mock.times_called
anymore.
I've also made some attempts with a Rc
, but this didn't work either.
Upvotes: 1
Views: 1603
Reputation: 76
The issue is actually with your trigger_listeners function rather than with the specific typing itself.
You're starting into the world of generics here and it's worth a read through the https://doc.rust-lang.org/rust-by-example/generics.html to get a better understanding, but to get started all you need to do is modify the signature of your trigger_listeners
function a bit.
You currently have
pub fn trigger_listeners(listeners: Vec<&mut Box<dyn Listener>>) {
for mut x in listeners {
x.trigger();
}
}
In rust, traits play a double-role where they are also considered types to an extent. So you need to generalize your method signature in order to reflect that.
pub fn trigger_listeners<T: Listener>(listeners: Vec<&mut Box<T>>) {
for mut x in listeners {
x.trigger();
}
}
Here we're saying trigger_listeneres should accept any type T
where that type implements the Listener
trait versus passing the trait itself in the type signature.
Edit:
As trentctl pointed out and based on the fact you need vecs of a non-contigous type to be able to be passed to the trigger_listeners
function, we need to modify a few things.
1) We can actually do this without Box in order to simplify things.
let mut mock = Mock { times_called: 0 };
let mut mock2 = Mock2 { times_called: 0 };
let items: Vec<&mut dyn Listener> = vec![&mut mock, &mut mock2];
trigger_listeners(&mut items);
2) To accept a vec of Unsized dyn Listeners, we need to add the ?Sized trait to the trait bounds of the trigger_listeners function.
pub fn trigger_listeners<t>(listeners: &mut Vec<&mut T>)
where T: Listener + ?Sized
{
for x in listeners {
x.trigger();
}
}
Another point, if you aren't expecting the types that you need to pass to the trigger_listener function to change, you can possibly step away from using trait objects and instead wrap your types in an enum type and pass a vec of those instead of trait objects. If, however you're expecting users of your library to extend the known types with their own implementing the Listener trait then trait objects is the right way to go about it.
Upvotes: 2