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.
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.
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.