Future cannot be sent between threads safely with Tauri

I'm using Tauri and was trying to call the get_total_pages async function. If I use the code directly in the download_file async function, it works, but not when I make the logic into a separate function.

I get this error:

future cannot be sent between threads safely
within `impl futures::Future<Output = Result<Vec<std::string::String>, std::string::String>>`, 
the trait `std::marker::Send` is not implemented for `Rc<Node>`, 
which is required by 
`impl futures::Future<Output = Result<Vec<std::string::String>, std::string::String>>: std::marker::Send`

I have marked below where the error shows up and which line seems to cause it.

#[tauri::command] // ERROR here
async fn handle_download_file(source: &str, url: &str, batch_size: usize,) -> Result<Vec<String>, String> {
    source::download_file(source, url, batch_size).await
}
use futures::future::join_all;
use kuchikiki::{traits::*, NodeRef};

pub async fn download_file(url: &str, batch_size: usize) -> Result<Vec<String>, String> {
    let document = kuchikiki::parse_html().one("<html></html>");

    // ERROR because of this line
    let total_pages = get_total_pages(&document).await;

    Ok(vec![])
}

async fn get_total_pages(document: &NodeRef) -> usize {
    let mut total_pages = 1;

    let last_page_node = match document.select("#list-chapter ul.pagination > li.last a") {
        Ok(mut nodes) => nodes.next(),
        Err(_) => None,
    };

    match last_page_node {
        Some(node) => {
            let last_page_url = node.attributes.borrow().get("href").unwrap().to_string();
            let total_page = last_page_url
                .split("=")
                .last()
                .expect("Couldn't get last split at '='")
                .parse::<usize>()
                .unwrap_or(1);
            total_pages = total_page;
        }
        None => {
            total_pages = 1;
        }
    };

    return total_pages;
}

On a test repo: The error happens here because of this line

Disclaimer: I'm very new to Rust and this may be a dumb question.

It seems it happens when I try to await after using Kuchikiki for parsing html.

As a helpful tip, looking at the full error message that is printed to your terminal when you run cargo check in your project is often times better, as IDEs are notorious for removing useful output from error messages.

As to your error, kuchikiki::NodeRef can't be shared between threads because it is !Send as it stores an Rc (not threadsafe reference counted pointer type) internally.

To fix your problem, avoid keeping a NodeRef across .await points. When you hit an .await, your future might get suspended and rescheduled to a different thread, so the state captured by the future must be Send.[1] get_total_pages does not need to be async (you never .await anything in it) so just making it synchronous should be enough to solve your problem.


  1. This is not an inherent part of the Future API—i.e. futures don't need to be Send per se—but an implementation detail of the multithreaded async Tokio runtime used under the hood of Tauri. ↩︎

5 Likes

Thank you, I needed to await for another function as well, but moving the kuchikiki (Rc) code into a separate non-async function fixed the issue.

1 Like

I forgot to add that if you are parsing giant html files synchronously, you might end up blocking the runtime (for too long), which you should avoid. In that case, you might want to spawn a blocking task specific to your html parsing with spawn_blocking and communicate its result back to your async task through a channel. You can read more about how to avoid blocking your asynchronous runtime here:

1 Like