Passing trait bounded structs to mpsc channels

This might be a really dumb thing to do but I'm exploring it anyway as a way to understand more about Rust.

I've created a trait and a struct that uses it in a trait bound:

trait State {}

struct Timer<T: State> {
    state: T,
    update: fn(T),
}

I've also created a couple of other structs that implement the trait:

struct Character { hitpoints: u32 }
impl State for Character {}
impl Character {
    fn update(mut self) {
        self.hitpoints += 10;
    }
}

struct Monster { hitpoints: u32 }
impl State for Monster {}
impl Monster {
    fn update(mut self) {
        self.hitpoints += 10;
    }
}

Now I want to create a mpsc channel and send an instance of each of the State implementing structs to it, but I get an error:

    let character = Character { hitpoints: 100 };
    let monster = Monster { hitpoints: 200 };
    
    let character_timer = Timer {
        state: character,
        update: Character::update,
    };

    let monster_timer = Timer {
        state: monster,
        update: Monster::update,
    };

    let (tx, rx) = mpsc::channel();

    tx.send(monster_timer).unwrap();
    tx.send(character_timer).unwrap();  // error[E0308]: mismatched types
    // ---- ^^^^^^^^^^^^^^^ expected `Timer<Monster>`, found `Timer<Character>`

I understand that the first time the channel is used the type of the mpsc Sender is inferred to be Timer<Monster> and this is causing the error. But is there any way to give it a type that allows sending a Timer with any type that implements State?

    let character_timer = Box::new(move || character.update());
    let monster_timer = Box::new(move || monster.update());

    let (tx, rx) = mpsc::channel();

    tx.send(monster_timer as Box<dyn FnOnce()>).unwrap();
    tx.send(character_timer).unwrap();

A few things:

You don't want a concrete type, you want a trait object. And that trait has to be implemented by the types you are trying to erase (i.e. it has to be implemented by Timer, not by the type parameters to Timer.

Your Timer structure is, at least in this example, just a single-use callback. So I switched to using the existing trait for exactly this: FnOnce.

Everything here is single-shot because you've used mut self instead of &mut self for the update methods. If you changed to &mut self, you'd want to use FnMut instead.

If you want to add functionality to Timer itself and avoid having to manually specify closures, you'd need a trait implemented for Timer<T> (let's call it Update), and pass Box<dyn Update> to the channel. So something like:

use std::sync::mpsc;

trait Update {
    fn update(self);
}

struct Timer<T: Update> {
    state: T,
}

struct Character { hitpoints: u32 }

struct Monster { hitpoints: u32 }

impl Update for Character {
    fn update(mut self) {
        self.hitpoints += 10;
    }
}

impl Update for Monster {
    fn update(mut self) {
        self.hitpoints += 10;
    }
}

impl<T> Update for Timer<T> where T: Update {
    fn update(self) {
        self.state.update();
    }
}

fn main() {
    let character = Character { hitpoints: 100 };
    let monster = Monster { hitpoints: 200 };
    
    let character_timer = Timer {
        state: character,
    };

    let monster_timer = Timer {
        state: monster,
    };
    
    let (tx, _) = mpsc::channel();

    tx.send(Box::new(monster_timer) as Box<dyn Update>).unwrap();
    tx.send(Box::new(character_timer)).unwrap();
}

You would still want to change update to not use plain old self, though. As it stands, once you create the timer and put it in the channel, you can't ever look at the underlying state value ever again. Heck, once you call update, the state is gone, which makes the whole thing pretty useless. But how to address that depends heavily on what it is you're trying to do beyond a contrived example.

Thanks for the help! This was actually a simplified version of the real code I'm trying to write. I think I probably simplified it too much :sweat_smile:

I was experimenting with using a closure for the callback, and then wanted to try using function pointers which lead to the question here. Your examples really helped me understand what was going on so thanks again. I've taken the code you wrote and adapted it a little to end up with this:

use std::thread;
use std::sync::mpsc;

trait State { fn update(&mut self); }

struct Character { hitpoints: u32 }

impl State for Character {
    fn update(&mut self) {
        self.hitpoints -= 1;
    }
}

struct Monster { anger: u32 }

impl State for Monster {
    fn update(&mut self) {
        self.anger += 10;
    }
}

trait Callback { fn callback(&mut self); }

struct Timer<T: State> {
    state: T,
}

impl<T> Callback for Timer<T> where T: State {
    fn callback(&mut self) {
        self.state.update();
    }
}

fn main() {
    let character = Character { hitpoints: 100 };
    let monster = Monster { anger: 0 };

    let character_timer = Timer { state: character };
    let monster_timer = Timer { state: monster };

    let (tx, rx) = mpsc::channel();

    tx.send(Box::new(monster_timer) as Box<dyn Callback + Send>).unwrap();
    tx.send(Box::new(character_timer)).unwrap();

    thread::spawn(move || {
        for mut timer in rx.recv() {
            timer.callback();
        }
    });
}

That's still a simplified example, in the real code I am parameterising the amount hitpoints and anger can be changed by instead of hardcoding it to -1 and 10 in the update() functions, a timer can run multiple times and contains properties to keep track of that, and there are 3 different threads responsible for registering timers, prioritising them and executing them.

Here's the equivalent, simplified version I've written using a closure for the callback instead:

use std::thread;
use std::sync::mpsc;

struct Character { hitpoints: u32 }

struct Monster { anger: u32 }

struct Timer {
    callback: Box<dyn FnMut() -> () + Send>,
}

fn main() {
    let mut character = Character { hitpoints: 100 };
    let mut monster = Monster { anger: 0 };

    let character_timer = Timer {
        callback: Box::new(move || {
            character.hitpoints -= 1;
        }),
    };

    let monster_timer = Timer {
        callback: Box::new(move || {
            monster.anger += 10;
        }),
    };

    let (tx, rx) = mpsc::channel();

    tx.send(character_timer).unwrap();
    tx.send(monster_timer).unwrap();

    thread::spawn(move || {
        for mut timer in rx.recv() {
            (timer.callback)();
        }
    });
}

Would appreciate any feedback on the two approaches, especially any performance gotchas.

Next I'm going to be looking at updating the code so that a state struct can still be read by other pieces of code whilst a Timer that needs to mutate it is waiting to run.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.