Search code examples
rustimgui

Pass non-clonable, non-static object to thread requiring static data


I'm using the ImGui Rust binding, and I have a big problem: I need to access the 'system' variable of the window from another thread: this variable grants control to several functions e.g. changing window size. I need to access this variable in the .build(ui, || { }) block. This code is taken (but shortened) from the example at: https://github.com/Gekkio/imgui-rs/blob/master/imgui-examples/examples/hello_world.rs

use imgui::*;

mod support;

fn main() {
    let system: support::System = support::init(file!());
    system.main_loop(move |_, ui| {
        Window::new(im_str!("Hello world"))
            .size([300.0, 110.0], Condition::FirstUseEver)
            .build(ui, || {
                // Do stuff with the UI here.
            });
    });
}

This is a problem, since the event loop function (That is the lambda in the .build() function) must use 'move' to pass ownership of all variables, and thus they essentially require a static lifetime.

I've came across several posts with people that almost have the same problem, but my data is non-static, and non-copyable. That is where the headache occurs.

Since I need a global state as well, it would be a good idea to wrap this variable in a struct. I managed to make it so that the struct can be passed to the thread without problems, but the 'system' variable still refuses to work.

Here are some examples of the scenarios that I've tried:

struct State {
    system: Option<support::System>
}

fn main() {
    let state = Arc::new(State {
        system: Some(support::init(file!()))
    });

    ERROR -> state.system.unwrap().main_loop(move |x: &mut bool, ui: &mut Ui| {});
}

This doesn't work, for the following reason:

cannot move out of an Arc move occurs because value has type std::option::Option<support::System>, which does not implement the Copy traitrustc(E0507) main.rs(31, 5): consider borrowing the Option's content

Borrowing won't work, so this is definitely annoying. Then we just store a reference to support::System in the struct, and that will solve it, right?

struct State<'a> {
    system: Option<&'a support::System>
}

fn main() {
    let system = support::init(file!()); 
    let state = Arc::new(State {
        ERROR -> system: Some(&system)
    });
}

This doesn't work because the Rust compiler thinks that the data won't live long enough (I know it will, but the compiler can't determine it properly)

system does not live long enough borrowed value does not live long enoughrustc(E0597) main.rs(59, 1): system dropped here while still borrowed main.rs(25, 17): argument requires that system is borrowed for 'static

Okay, that sounds reasonable. Then just use a Box<> to allocate it on the heap and forget about the whole problem.

struct State {
    system: Box<support::System>
}

fn main() {
    let state = Arc::new(State {
        system: Box::new(support::init(file!()))
    });

    let state1 = state.clone();
    let system1 = &state1.system;
    ERROR -> system1.main_loop(move |x: &mut bool, ui: &mut Ui| {
        Window::new(im_str!("Main"))
        .flags(WindowFlags::NO_DECORATION | WindowFlags::NO_RESIZE)
        .position([0f32, 0f32], Condition::Always)
        .build(ui, || {
            let state = state.clone();
            THIS WORKS! -> let system = &state.system;
            // Do stuff with the 'system' variable.
        });
    });
}

This results in an error with a slightly more complex type:

cannot move out of **system1 which is behind a shared reference move occurs because **system1 has type support::System, which does not implement the Copy traitrustc(E0507)

What? I'm trying to access a reference, I don't want to copy anything.

I am out of ideas. I have pretty much the exact same code in C++ and that works without problems, because C++ doesn't care about variable ownership: you can pass around the 'state' pointer as much as you want.

I'd also like to add that I've tried the same Arc<> scenario by wrapping the 'State' in a Arc<Mutex<>>. It results in one of the same errors that the examples trigger. I've also tried to work with a static version of the struct, but that yields problems that it's immutable, and I believe that it threw the same ownership errors.

I have a feeling that this is very easy to solve, but it does require some (advanced) experience in the Rust borrowing mechanic. I am new to Rust (Less than a week of hobby experience) and I can grasp a lot of concepts, but I essentially need to avoid the ownership mechanism here, since the data must be globally shared.

I hope that an experienced Rustacean knows the solution to this! Thanks a lot in advance.


Solution

  • I discovered that I was looking in the wrong direction: I was trying to access stuff from the top of main() in the .build(ui, || {}) block, but what I should have been doing was retrieving that info from system.main_loop() as parameter. A global state with trivial variables should still be done as I attempted previously, but when you need a reference to window internals, you do need a different solution.

    I made the following changes: In mod.rs (This file is included in the linked ImGui examples), change the signature of F in main_loop() to pass a &Display: pub fn main_loop<F: FnMut(&mut bool, &Display, &mut Ui) + 'static>(self, mut run_ui: F)

    Then in said function, change the call to run_ui() to pass a reference of the display variable. This function already possesses all of these internal variables, so you can directly access it: run_ui(&mut run, &display, &mut ui); After doing so, you can now edit your lambda to main_loop to receive this new parameter. You can then do exciting things with the window context!

    system.main_loop(move |x: &mut bool, display: &glium::Display, ui: &mut Ui| {
        let gl_window = display.gl_window();
        let window_size = gl_window.window().inner_size();
    
        Window::new(im_str!("Main"))
            .position([0f32, 0f32], Condition::Always)
            .size([window_size.width as f32, window_size.height as f32], Condition::Always) // <- We can now use window internals here!
            // etc, build your window as usual
    });
    

    Thanks for your comments guys, and I hope that this may help others in the future!