rust

How to select more implementations for struct selected via dynamic trait?


I have a project written using egui (irrelevant) that opens other subviews. Code has been edited for conciseness. I turned all the files into one so it can be ran in the playground. permalink, gist

use std::rc::Rc;
use std::{cell::RefCell, collections::BTreeMap, rc::Weak};
// === REUSABLE OBJECTS ===
// individual ui element in d`ropdown
pub enum MenuItem<'a> {
    Seperator,
    Button(&'a str, &'a str, Box<dyn Fn()>),
}
// subview (views/)
pub trait View<'a> {
    // ran once every second to draw frame
    fn update(&mut self);
}
// the subview (views/) that is currently selected, Blank default
pub enum ViewState {
    Blank,
    ModelExtractor,
}
/// === MAIN.RS ===
pub struct MyApp<'a> {
    // current subview choice
    current_view_sel: ViewState,
    view_changed: bool,
    // actual subview (views/)
    current_view: Option<&'a mut Box<dyn View<'a> + 'a>>,
    // passed as Weak<...> to subview
    menus: Rc<RefCell<BTreeMap<&'a str, Vec<MenuItem<'a>>>>>,
}
impl<'a> MyApp<'a> {
    // Called once before the first frame.
    pub fn new() -> Self {
        Self {
            current_view_sel: ViewState::Blank,
            view_changed: true,
            current_view: None,
            menus: Rc::new(RefCell::new(BTreeMap::new())),
        }
    }
    // Called each time the UI needs repainting, which may be many times per second.
    fn update(&mut self) {
        //menu bar is drawn
        println!("Menu bar drawn");
        //assume button clicked and variable changed
        {
            self.current_view_sel = ViewState::ModelExtractor; // sets what is *supposed* to be drawn below
            self.view_changed = true;
        }
        if self.view_changed {
            self.menus.borrow_mut().clear();
            self.current_view = match self.current_view_sel {
                ViewState::ModelExtractor => Some(&mut Box::new(ModelExtractor::new(
                    Rc::downgrade(&self.menus),
                ))),
                _ => None,
            };
            self.view_changed = false; // Reset the flag after handling the change
        }
        // more ui stuff
        // ...
        if let Some(view) = self.current_view.as_mut() {
            // let the subview draw itself here
            view.update();
        }
    }
}

// === MODEL_EXTRACTOR.RS ===
pub struct ModelExtractor<'a> {
    menus: Weak<RefCell<BTreeMap<&'a str, Vec<MenuItem<'a>>>>>,
}
impl<'a> ModelExtractor<'a> {
    pub fn new(menus: Weak<RefCell<BTreeMap<&'a str, Vec<MenuItem<'a>>>>>) -> Self {
        Self { menus }
    }
}
impl<'a> View<'a> for ModelExtractor<'a> {
    /// Called each time the UI needs repainting, which may be many times per second.
    fn update(&mut self) {
        println!("VIEW: Model extractor drawn")
    }
}
fn main() {
    // create main app
    let mut app = Box::new(MyApp::new());
    // "draw" it a few times
    for _ in 0..3 {
        app.update();
    }
}

MyApp needs to own menus. Thanks in advance,

I tried running it. I expected it to run. It failed with the compiler messages:

error[E0308]: mismatched types
   --> src/main.rs:51:51
    |
51  |                   ViewState::ModelExtractor => Some(&mut Box::new(ModelExtractor::new(
    |  ______________________________________________----_^
    | |                                              |
    | |                                              arguments to this enum variant are incorrect
52  | |                     Rc::downgrade(&self.menus),
53  | |                 ))),
    | |__________________^ expected `&mut Box<dyn View<'_>>`, found `&mut Box<ModelExtractor<'_>>`
    |
    = note: expected mutable reference `&mut Box<(dyn View<'a> + 'a)>`
               found mutable reference `&mut Box<ModelExtractor<'_>>`
    = help: `ModelExtractor<'_>` implements `View` so you could box the found value and coerce it to the trait object `Box<dyn View>`, you will have to change the expected type as well
help: the type constructed contains `&mut Box<ModelExtractor<'_>>` due to the type of the argument passed
   --> src/main.rs:51:46
    |
51  |                    ViewState::ModelExtractor => Some(&mut Box::new(ModelExtractor::new(
    |   ______________________________________________^    -
    |  |___________________________________________________|
52  | ||                     Rc::downgrade(&self.menus),
53  | ||                 ))),
    | ||__________________-^
    | |___________________|
    |                     this argument influences the type of `Some`
note: tuple variant defined here
   --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:599:5
    |
599 |     Some(#[stable(feature = "rust1", since = "1.0.0")] T),
    |     ^^^^

For more information about this error, try `rustc --explain E0308`.

I have tried making Menu a variety of references and smart pointers but nothing has worked. I have tried searching Reddit, Stack Exchange, and other places to find a solution, but I apologize if this is a duplicate.


Solution

  • You can simply remove the mutable reference to the Box inside the Option.

    pub struct MyApp<'a> {
        // current subview choice
        current_view_sel: ViewState,
        view_changed: bool,
        // actual subview (views/)
        current_view: Option<Box<dyn View<'a> + 'a>>,
        // passed as Weak<...> to subview
        menus: Rc<RefCell<BTreeMap<&'a str, Vec<MenuItem<'a>>>>>,
    }
    

    And instead of passing Some(&mut Box::new...) only pass Some(Box::new...)

    self.current_view = match self.current_view_sel {
           ViewState::ModelExtractor => Some(Box::new(ModelExtractor::new(
              Rc::downgrade(&self.menus),
           ))),
           _ => None,
    };
    
    

    This occurs mainly because the coercion to dyn with Box only happens for the value of the Box itself and not a reference to it.

    Another problem that your code generates by using &'a mut Box::new() is that the reference will never live long enough, it will only live within the block of code. Since you do not consume self (you are using &mut self, a reference), it will die at the end of the scope and you would end up with a dangling reference.


    Edit

    Just to explain the issue of lifespan better because I think it could be misunderstood the way I had spoken.

    In fact, in your code, if you had an struct with lifetime 'a, the way you wrote it there:

    pub struct MyApp<'a> {
        ...
        current_view: Option<&'a mut Box<dyn View<'a> + 'a>>,
        ...
    }
    

    Your current_view property would have to live at least a 'a duration. If in your update function you do something like this:

    self.current_view = match self.current_view_sel {
           ViewState::ModelExtractor => Some(&mut Box::new(ModelExtractor::new(
              Rc::downgrade(&self.menus),
           ))),
           _ => None,
    };
    

    You are saying that the value created by &mut Box::new(...) has a lifetime of 'a, where 'a corresponds to at least the same lifetime as self, which is untrue, since you create a &mut Box<...> whose lifetime is tied only to the scope of the current function, while self is a reference and lives beyond the scope of the function, thus making &mut Box<...> not have the lifetime of 'a. For this to work, you would need self to have the same lifetime as the function scope (consuming self as an argument of the function, using self without &mut), then your &mut Box<...> would have the same lifetime as self, which would correspond with the lifetime 'a. Or if you really need a &mut Box<...>, you can receive it as a reference via argument, but you will still face the same problem with the creation of the Box, because as stated before, the coercion of Box<dyn Trait> only occurs on the value itself.