An ownership issue while using fltk.rs

This is an ownership problem which is why I'm posting here instead of on the fltk.rs github forum. Here is the code I need help with:

use fltk::{prelude::*, *, window};
use fltk::enums::CallbackTrigger;

fn main() {
    let mainapp = app::App::default();
    let mut mainwin = window::Window::default()
        .with_size(800, 500)
        .with_label("Main Window");
    mainwin.make_resizable(true);
    mainwin.end();
    mainwin.show();

    let value = input_prompt(&mainapp, "This is a prompt");
    mainapp.run().unwrap();
    println!("\n In main() the inputted value is:  {:?}", value);
}

fn input_prompt(app: &app::App, prompt: &str) -> String {
    let mut win = window::Window::default()
        .with_size(400, 300)
        .with_label("Input Window");
    win.make_resizable(true);

    let flex = group::Flex::default()
        .with_size(100, 100)
        .column()
        .center_of_parent();

    let _prompttext = frame::Frame::default().with_label(prompt);

    let mut input = input::Input::default();
    input.set_trigger(CallbackTrigger::EnterKey);
    let input = input.clone();
    let mut win = win.clone();

    input.set_callback(move|input| {
        println!("\n In the input callback, the value is:  {}", input.value());
        win.hide();
    });

    flex.end();
    win.end();
    win.show();

    while win.shown() {
        app.wait();
    }

    input.value()
}

The problem occurs in lines 36-39 where an attempt is made to set a callback on an input window. The idea is that the app will first open a main window for doing other stuff, but when an input window is needed, this routine will call up an input_prompt() window, to allow data entry. On pressing enter the window is closed and the data returned to the calling function. The compiler is objecting to this code (rightfully so) because ownership of the input window is being passed into the callback closure container located in the lines 36-39. I really don't know how to address this problem, any ideas/suggestions?

I guess you are providing a move closure to make it 'static? If so, you should probably use shared ownership (ie., Rc or Arc) here, to make the widgets "cloneable" without needing to actually clone or borrow them.

In this case, you do not need more shared ownership, since fltk has you covered. Its Clone implementations already do reference counting for you.

To make this compile, you need to avoid the variable shadowing the reference that you use outside of the callback. The naive solution is to just give the clones unique names when moving them into the closure:

    let mut win2 = win.clone();
    input.set_callback(move |input| {
        println!("\n In the input callback, the value is:  {}", input.value());
        win2.hide();
    });

This does work, but it makes code difficult to read. (Also note that input does not need to be cloned at all, because the closure receives a reference to it as an argument. So, delete that line entirely.)

A slightly nicer solution is to continue using variable shadowing with the introduction of a block scope:

    {
        let mut win = win.clone();
        input.set_callback(move |input| {
            println!("\n In the input callback, the value is:  {}", input.value());
            win.hide();
        });
    }

This also works, even though win is shadowed, the clone is only accessible until the end of its scope.

The idiomatic solution (though a lot harder to initially discover) is to put the block scope inside the function call argument and let the block expression evaluate to the closure referencing the clone:

    input.set_callback({
        let mut win = win.clone();

        move |input| {
            println!("\n In the input callback, the value is:  {}", input.value());
            win.hide();
        }
    });

Now it is clear that win is the same reference inside and outside the closure, and the clone is lexically scoped to "setting the callback". The intention is clear that the clone is only meant for the closure. It's a neat trick that allows you to run some setup code prior to creating the closure, and it doesn't pollute your local namespace.

3 Likes

Just for reference, DoubleWindow has an inner field which is cloned every time, when win.clone() is called. The inner field is just a widget wrapped in an Arc and therefore cloning it for this callback is absolutely justified.

Yes. That is the same thing I commented on.

Yes! That worked! Thank you, @parasyte.

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.