Rust + Iced + Tokio: Problem with async function

I am attempting to learn how to use Tokio and async functions. I want my program to startup and display a loading screen as quickly as possible and in parallel run a function to update an SQL database from a CSV file. Once the database is updated I would like the function to return a message to the iced update function so I can update several state variables and display a new screen. Below is my attempt using an async function "update_database" that is spawned via Tokio in the iced run function. It runs and the println's in the new and async functions print but the println in Message::GoToUpdateDataBaseScreen in the iced update function does not. So here is what I am trying to determine. 1) Does this code run the async function in parallel with the program booting up and displaying the loading screen and if not what have I done wrong. 2) How do I setup the update_database function so that it returns a message to the iced update function once it has completed. Below is the code. Any input will be appreciated.

async fn update_database<'a>() -> Message<> {

    println!("Starting database update...");  // prints second
    let record_data = match insert_csv_data_into_db().await {
        Ok(data) => {
            data
        },
        Err(_) => {
            return Message::GoToUpdateDataBaseScreen(0, 0, 0)
        }
    };
    println!("Database update completed. Records read: {}, Records inserted: {}, Records updated: {}", record_data[0], record_data[1], record_data[2]);  // prints third
    Message::GoToUpdateDataBaseScreen(record_data[0], record_data[1], record_data[2])
}

// #[derive(Debug, Clone)]
pub struct State {
    pub records_read: u64,
    pub records_inserted: u64,
    pub records_updated: u64,
    pub screen: Screen,
    pub closing_values: Vec<QueryResults>,
    pub heading: String,
}

impl State {

    fn new() -> Self {

        let rt = tokio::runtime::Runtime::new().unwrap();
        rt.spawn(update_database());
        println!("State initialized with loading screen.");  // prints first

        Self {
            records_read: 0,
            records_inserted: 0,
            records_updated: 0,
            screen: Screen::LoadingScreen,
            closing_values: Vec::new(),
            heading: String::new(),
        }
    }

    fn update(&mut self, msg: Message) -> Task<Message> {

        match msg {

            Message::GoToUpdateDataBaseScreen(records_read, records_inserted, records_updated) => {
                
                println!("Updating database screen with records read: {}, records inserted: {}, records updated: {}", records_read, records_inserted, records_updated);  // never prints
                self.screen = Screen::UpdateDatabaseScreen;      
                self.records_read = records_read;
                self.records_inserted = records_inserted;
                self.records_updated = records_updated;
                Task::none()
            }

            Message::Exit => {
                std::process::exit(0);
            }

            Message::GoToHomeScreen => {

                self.screen = Screen::HomeScreen;
                self.records_read = 0;
                self.records_inserted = 0;
                Task::none()
            }

            Message::GoToMinCloseScreen | Message::GoToMaxCloseScreen | Message::GoToAllCloseScreen => {

                self.screen = Screen::ClosingScreen;

                let query_results = match msg {
                    Message::GoToMinCloseScreen => get_close(QueryType::Min),
                    Message::GoToMaxCloseScreen => get_close(QueryType::Max),
                    Message::GoToAllCloseScreen => get_close(QueryType::All),
                    _ => unreachable!(),
                };

                let query_results = match query_results {
                    Ok(results) => results,
                    Err(e) => {
                        if let IoSqlError::IoError(io_err) = e {
                            println!("I/O error details: {}", io_err);
                        } else if let IoSqlError::SqlError(sql_err) = e {
                            println!("SQL error details: {}", sql_err);
                        }
                        return Task::none();
                    }
                };

                self.heading = match msg {
                    Message::GoToMinCloseScreen => "Minimum Closing Prices".to_string(),
                    Message::GoToMaxCloseScreen => "Maximum Closing Prices".to_string(),
                    Message::GoToAllCloseScreen => "All Closing Prices".to_string(),
                    _ => unreachable!(),
                };

                self.closing_values = query_results;
                Task::none()

            }

        } // end of match statement
    }

    fn view(&self) -> iced::Element<'_, Message> {

        match self.screen {

            Screen::LoadingScreen => {
                loading_view()
            }
            Screen::HomeScreen => {
                home_view()
            } 
            Screen::UpdateDatabaseScreen => {
                update_db_view(self.records_read, self.records_inserted, self.records_updated)
            } 
            Screen::ClosingScreen => {
                closeing_view(&self.heading, self.closing_values.as_ref())
            }
        } 
    } // end of view function

}

the async function starts running in parallel but doesn't have a chance to finish.

the task is spawned onto a runtime that is bound to a local variable rt inside the new() function, which means the runtime is dropped once the new() function returns, so all the tasks will be canceled at that point.

fix: either move the rt into your app state, or to a larger scope that outlives the iced main loop, such as in the main function or a static/thread_local variable.

you cannot "return" a value from a task that is running in-parallel (more generally, concurrently) with the "calling" function. you must send the value via other means.

in a GUI application, this depends on the how the GUI's main event loop is implemented. for iced, this is done via the Task and Subscription mechanism, see this section of the docs:

in this particular use case, I suggest you to wrap your async function as an iced::Task and return it from the boot property of your application. note your wrapper still need to manage the tokio runtime manually, the iced async runtime doesn't have a tokio IO reactor.

imo, the architecture of the main event loop and lifecycle of a GUI application (e.g. how the boot() interact with async tasks) is not documented enough for iced, or at least I can't find them easily.

Don't worry about handling tokio runtimes yourself at all. Assuming you've got the tokio feature enabled, iced will manage the runtime for you, and all you need to do is return Tasks which themselves return a given Message that you can handle in your update method.

You can either return Tasks from your update method, to run asynchronous code, or, if you need to run something asynchronous at startup, you can additionally return a Task from your new method.

In your OP, you are returning Self from new, but it is also valid to return (Self, Task). See here

So, try returning (Self {..}, Task::future(update_database)) from new.