0

I am building a TUI application in Rust and I wish to broadcast keypress events for different parts of the application to listen to. Initially, I create a struct that handles the events:

use std::rc::Rc;
use crate::global::cursor::CursorAction;
use crate::global::state::State;

pub trait Observer {
    fn on_keypress(&mut self, cursor_action: &CursorAction, old_state: &State);
}

#[derive(Default)]
pub struct EventHandler {
    listeners: Vec<Rc<dyn Observer>>
}

impl EventHandler {
    pub fn register(&mut self, observer: Rc<dyn Observer>) {
        self.listeners.push(observer);
    }

    pub fn notify(&mut self, cursor_action: &CursorAction, old_state: &State) {
        for listener in &mut self.listeners {
            listener.on_keypress(cursor_action, old_state);
        }
    }
}

And to register this state, I do this in the src/lib.rs file:

use std::error::Error;
use std::ops::Deref;
use std::rc::Rc;
use crate::event_handler::{EventHandler, Observer};

use crate::global::state::State;

mod terminal;
mod global;
mod domain;
mod event_handler;

pub fn start() -> Result<(), Box<dyn Error>> {
    let mut event_handler = EventHandler::default();
    let mut state: Rc<dyn Observer> = Rc::new(State::default());
    event_handler.register(Rc::clone(&state));
    init(&mut state);

    let mut terminal = terminal::setup_terminal()?;
    terminal::run(&mut terminal, &mut state, event_handler)?;
    terminal::restore_terminal(&mut terminal)?;
    Ok(())
}

fn init(state: &mut Rc<State>) {
    let raw_files = domain::retrieve_files();
    state.set_files(raw_files);
}

However, I get an error:

    init(&mut state);
    ---- ^^^^^^^^^^ expected `&mut Rc<State>`, found `&mut Rc<dyn Observer>`

When I have impl Observer for State defined. I am failing to implement this pattern in Rust and believe that it is quite difficult to do it. How do I make this implementation work? Is there any alternate approach to using this pattern?

Nishant Jalan
  • 844
  • 9
  • 20
  • 1
    You should not try to remove the concrete type from the `state` variable but instead cast the `Rc::clone()` when passing it to `register` as you can see in this question: [Error when passing Rc as a function argument](https://stackoverflow.com/questions/63893847/error-when-passing-rcdyn-trait-as-a-function-argument) – cafce25 Aug 18 '23 at 18:45
  • 1
    It's a bit weird to pass around `&mut Rc` the only methods that deal with it (`get_mut` and `make_mut`) seem counter to your intentions, you should probably use interior mutability instead. i.e. `Rc` and `Rc>` instead of the mutable references. – cafce25 Aug 18 '23 at 18:55
  • Why do you even use `Rc` in the first place? Why not a `Box`? – Chayim Friedman Aug 19 '23 at 18:09

1 Answers1

0

Even assuming Observer trait is implemented for State somewhere, Rust doesn't have downcasting from Rc<dyn Observer> to Rc<State>.

Your first culprit is this line:

let mut state: Rc<dyn Observer> = Rc::new(State::default());

what creates polymorphic state variable, but without runtime information about original type.

But as others mention in comments, your use of &mut Rc<> is wrong - it makes reference counting pointer mutable, not the instance of State.

Now, how I see it (I'm betting w/o test as you don't provide enough code):

  • Your init() doesn't need &mut Rc<State> but at most &mut State (btw. why not do this in constructor?)
  • let mut state = Rc::new(RefCell::new(State::default()));
  • Call init(&**state)

If you provide some working example I'd refactor it to what I believe be satisfactory code w/less complications.

Michał Fita
  • 1,183
  • 1
  • 7
  • 24