[SOLVED] New Boot trait: no longer able to use a capturing closure to initialize application state

I was able to move data into the context of a closure that would return my application state, but that’s no longer possible because now the closure must implement Fn instead of FnOnce. This was possible in 0.13, is this by design?

This is what my code used to look like:

impl State {
    fn new(db_connection: rusqlite::Connection, args: clap::ArgMatches) -> (Self, iced::Task<Message>) {
        ...
    }

pub fn main() -> iced::Result {
    let matches = clap::Command::new("App").arg(...).get_matches();
    let connection = get_db_connection();
    // execute some queries in the DB that are required to initialize my iced::Settings
    return iced::application(...).run_with(|| State::new(connection, clap_matches));
}
1 Like

iced::application(State::new, State::update, State::title).title("my cool 0.14 app").run()

And how does State::new receive its parameters?

same thing I think?

pub fn main() -> iced::Result {
    let counter = 100;
    iced::application(move || App::new(counter), App::update, App::view)
        .run()
}

That’s what doesn’t work anymore (or maybe that one does because counter is Copy, but let’s assume it isn’t) because that closure will be a FnOnce and not a Fn which is now the required type.

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
   --> src/main.rs:709:30
    |
709 |     return iced::application(move || Vimini::new(matches, db_connection), Vimini::update, Vimini::view)
    |            ----------------- ^^^^^^^------------------------------------
    |            |                 |                   |
    |            |                 |                   closure is `FnOnce` because it moves the variable `matches` out of its environment
    |            |                 this closure implements `FnOnce`, not `Fn`
    |            |                 the requirement to implement `Fn` derives from here
    |            required by a bound introduced by this call

I can always clone the data but it still looks like a step back from what was possible before.

Faced with the same issue. I was trying to find any places in iced where boot is invoked and found only two

  • impl Program for Attach which is used for debugging purposes, as I can see. There
  • impl<P: Program> Instance<P> ... new(program: P) which is the only way to create an Instance<P> and it can be called only once. There

And I expect this kind of code might be safe enough right now (but, of course, it’s just a hacky workaround)

    let once_boot = RefCell::new(Some((app_state, task)));
    let boot = move || match once_boot.borrow_mut().take() {
        Some(v) => v,
        None => panic!(),
    };

You can use boot Fn as a boot arg in iced::application

1 Like

Ah, that’s a nice hack, I wanted a closure that would take the value from an Option the first time and panic the other times but I wasn’t clever enough to code it, I always ended up with my closure implementing FnMut instead of FnOnce. Thanks for this.

As to the motivation, I wonder if the new daemon type of program maybe needs to call boot multiple times.

I’m not sure, but when I was diving into the code, daemon looked like an application which is able to work without any window.

I was answered in discord about the specific cases when State might be created twice - it’s new time-travel feature

Ah, that makes sense. Thanks again!