Cannot create BTree of dyn <Trait>

I have UI components, each component is a struct that implement trait UIComponent. Also I have UI renderer which render all this components.

I want to store all created components inside BTree. So this way I can keep component instances (in case they have some state). I want to avoid using stateless components and avoid keeping all data within UI.
(To be honest I do not know how to do this better).

This is my code:

pub struct UI<'a> {
    _terminal: Terminal<CrosstermBackend<Stdout>>,
    _components: BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>>,
}

impl<'a> UI<'a> {
    pub fn new() -> Self {
        enable_raw_mode().unwrap();
        let mut stdout = std::io::stdout();
        execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
        let backend = CrosstermBackend::new(stdout);
        let mut _terminal = Terminal::new(backend).unwrap();
        _terminal.clear().unwrap();
        _terminal.hide_cursor().unwrap();

        Self {
            _terminal,
            _components: Self::init_components(),
        }
    }

    fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
        let mut components = BTreeMap::new();
        components.insert("DebugPanel", Box::new(DebugPanel::new())).unwrap();
        components.insert("Title", Box::new(Title::new())).unwrap();

        components
    }

    pub fn render(&mut self, options: UIOptions) {
        self._terminal.draw(|frame| {
            let chunks = Layout::default()
                .direction(Direction::Vertical)
                .margin(MARGIN)
                .constraints([
                    Constraint::Percentage(12),
                    Constraint::Percentage(76),
                    Constraint::Percentage(12),
                ])
                .split(frame.size());

            let output_panels = Layout::default()
                .direction(Direction::Horizontal)
                .constraints([
                    Constraint::Percentage(100),
                ])
                .split(chunks[1]);

            self._components.get("Title").unwrap().render(frame, chunks[0]);
            self._components.get("DebugPanel").unwrap().render(frame, output_panels[0]);

        }).unwrap();
    }
}

the problem here is that I have compile error:

error[E0308]: mismatched types
  --> src\ui\mod.rs:64:9
   |
59 |     fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
   |                             ----------------------------------------------------------------- expected `BTreeMap<&'a str, Box<(dyn UIComponent<CrosstermBackend<Stdout>> + 'static)>>` because of return type
...
64 |         components
   |         ^^^^^^^^^^ expected trait object `dyn UIComponent`, found struct `DebugPanel`
   |
   = note: expected struct `BTreeMap<&'a str, Box<(dyn UIComponent<CrosstermBackend<Stdout>> + 'static)>>`
              found struct `BTreeMap<&str, Box<DebugPanel<'_>>>`

Could somebody explain how to fix this issue ? Also I would appreciate if somebody can advice how better keep components for render in my case.

UPDATED. I think the main question I want to ask is how to refactor my code to allow instantiated components be available from outside of UI::render to let me keep its state.

Just in case, this is where I use UI:

async fn handle_ui(&mut self) -> JoinHandle<()> {
        let message_pipe = Arc::clone(&self._message_pipe);

        tokio::spawn(async move {
            let mut ui = UI::new();

            loop {
                ui.render(UIOptions {
                    message: message_pipe.lock().await.recv(),
                });

                sleep(Duration::from_millis(WRITE_TIMEOUT)).await;
            }
        })
    }

and this is how I want render function to be implemented:

pub fn render(&mut self, options: UIOptions) {
	self._terminal.draw(|frame| {
		let chunks = Layout::default()
			.direction(Direction::Vertical)
			.margin(MARGIN)
			.constraints([
				Constraint::Percentage(12),
				Constraint::Percentage(76),
				Constraint::Percentage(12),
			])
			.split(frame.size());

		let output_panels = Layout::default()
			.direction(Direction::Horizontal)
			.constraints([
				Constraint::Percentage(100),
			])
			.split(chunks[1]);

		match options.message {
			MessageType::Message(output) => {
				debug_panel.add_message(output);
			},
			MessageType::ChooseCharacter(_characters) => {
				// ...
			},
			MessageType::ChooseRealm(_realms) => {
				// ...
			},
		}

		title.render(frame, chunks[0]);
		debug_panel.render(frame, output_panels[0]);

	}).unwrap();
}

Should be as simple as helping out type inference a little bit with where the coercions need to happen.

    fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
        let mut components = BTreeMap::new();
        components.insert("DebugPanel", Box::new(DebugPanel::new()) as _).unwrap();
        components.insert("Title", Box::new(Title::new()) as _).unwrap();

        components
    }

alternatively, you could add a type signature earlier

    fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
        let mut components = BTreeMap::<_, Box<dyn UIComponent<_>>>::new();
        components.insert("DebugPanel", Box::new(DebugPanel::new())).unwrap();
        components.insert("Title", Box::new(Title::new())).unwrap();

        components
    }

or, if you like weirder solutions, add a fake return to help type inference

    fn init_components() -> BTreeMap<&'a str, Box<dyn UIComponent<CrosstermBackend<Stdout>>>> {
        let mut components = BTreeMap::new();
        if false {
            return x;
        }
        components.insert("DebugPanel", Box::new(DebugPanel::new())).unwrap();
        components.insert("Title", Box::new(Title::new())).unwrap();

        components
    }
2 Likes

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.