Search code examples
genericsrust

How can I accept a generic trait implementation to my struct and propagate it?


I have a case where one of my structs, Grid has a Vec of trait Listener implementations. When Grid is first created, this list is filled with Relay instances.

A Relay is a Listener which propagates incoming signals to the next Listener in line. The Relays are chained such that each relay knows of the next relay in line (see Grid::new).

However, the owner of Grid wants to create their own Listener to be called at the end of the chain of relays. Grid::new therefore accepts a Listener to use as the "next" listener in the last Relay.

Since the relays are all owned by Grid, but also need to be referenced mutably by another Relay, I've used Rc<RefCell<dyn Listener>> to manage the double ownership.

use rand::{thread_rng, Rng};
use std::cell::RefCell;
use std::rc::Rc;

trait Listener {
    fn on_signal(&mut self);
}

struct Relay {
    next: Rc<RefCell<dyn Listener>>,
}

impl Listener for Relay {
    fn on_signal(&mut self) {
        self.next.borrow_mut().on_signal();
    }
}

struct Receiver {}
impl Listener for Receiver {
    fn on_signal(&mut self) {
        println!("Signal Received!");
    }
}

struct Grid {
    relays: Vec<Rc<RefCell<Relay>>>,
}

impl Grid {
    fn new(listener: impl Listener) -> Self {
        Self {
            relays: (0..10).fold(Vec::new(), |mut acc, _| {
                acc.push(Rc::new(RefCell::new(Relay {
                    next: match acc.last() {
                        None => Rc::new(RefCell::new(listener)),
                        Some(listener) => listener.clone(),
                    },
                })));
                acc
            }),
        }
    }

    fn transmit(&mut self) {
        self.relays[thread_rng().gen_range(0..self.relays.len())]
            .borrow_mut()
            .on_signal();
    }
}

fn main() {
    Grid::new(Receiver {}).transmit();

    // But also
    Grid::new(Relay {
        next: Rc::new(RefCell::new(Receiver {})),
    })
    .transmit();
}

However, I can't get this to work. First I get

the parameter type `impl Listener` may not live long enough [E0310]

so based on this answer, I try to do

-    fn new(listener: impl Listener) -> Self {
+    fn new(listener: impl Listener + 'static) -> Self {

but this gives me

cannot move out of `listener`, a captured variable in an `FnMut` closure [E0507]

I'm not sure how to get this to work. I'm confused by impl vs dyn as well as fn vs Fn vs FnMut. A lot of people suggest Box<dyn Listener> but I'm using Rc<RefCell<dyn Listener>> so I don't think boxing should be necessary here? Also, it seems rather awkward to require callers of Grid::new to box their implementation.

How can I get my Grid::new method to work?


Solution

  • FnMut is a closure that can be called multiple times and can hold exclusive references in its captures.

    Now your closure

                |mut acc, _| {
                    acc.push(Rc::new(RefCell::new(Relay {
                        next: match acc.last() {
                            None => Rc::new(RefCell::new(listener)),
                            Some(listener) => listener.clone(),
                        },
                    })));
                    acc
                }
    

    in its None path, consumes listener. Because unfortunately the compiler can not see that this path is only taken once, it can only be called once. Fortunately it's not hard to convince the compiler that the code diverges if there is no value any more, simply wrap listener in an Option that we can Option::take and unwrap, now if there is no more value, the code would panic, the compiler can see this is ok:

    impl Grid {
        fn new(listener: impl Listener + 'static) -> Self {
            let mut listener = Some(listener);
            Self {
                relays: (0..10).fold(Vec::new(), |mut acc, _| {
                    acc.push(Rc::new(RefCell::new(Relay {
                        next: match acc.last() {
                            None => Rc::new(RefCell::new(listener.take().unwrap())),
                            Some(listener) => listener.clone(),
                        },
                    })));
                    acc
                }),
            }
        }
    }