1

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.

1

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

The suggested change is not a generalization because you can't pass Vec<&mut Box<dyn Listener>> anymore. You need + ?Sized on T to allow both (trait objects and concrete types).
Thanks. This worked for one type. I could add Mock to the vector. Unfortunately, if I create a second struct Mock2 which implements Listener, the compiler complains about: ^^^^^^^^^^ expected struct Mock, found struct Mock2``. I've read in the Rust book, that the dyn keyword is especially used for trait objects, but seem to have been removed in your solution? Does it play a role? Thanks :)
Right sorry, that's my fault, I made an assumption that you would be passing vecs of a strict type. I'll add an edit momentarily that accomodates vecs of trait objects. This is what trenctl is referring to in part above.
Thanks for the help! I've now managed to include it into my real application. Learned a lot about Rust while implementing it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.