Borrowing issue after wrapping the logic into a function

I had following code (partly, cli.db was not used after this part) that built, in which cli is a struct, db is PathBuf.

            let h = tokio::spawn({
                tracing::debug!("CLI interfacer starts");
                let uds = tarpc::serde_transport::unix::listen(&cli.uds, Bincode::default).await?;
                let mut perm = fs::metadata(&cli.uds).await?.permissions();
                perm.set_mode(perm.mode() | 0o222); // chmod a+w
                fs::set_permissions(&cli.uds, perm).await?;

Then I wrapped the inner block into a function:

async fn serv_cli(uds: &Path, server: Server) -> Result<()> {
    tracing::debug!("CLI interfacer starts");
    let listener = tarpc::serde_transport::unix::listen(uds, Bincode::default).await?;
    let mut perm = fs::metadata(uds).await?.permissions();
    perm.set_mode(perm.mode() | 0o222); // chmod a+w
    fs::set_permissions(uds, perm).await?;

And

let h = tokio::spawn(serv_cli(
                &cli.uds,
                Server {
                    db: cli.db,
                    inotify: inotify.clone(),
                    records: records.clone(),
                },
            ));

Now it breaks, "cli.uds" does not live long enough.

I could fix this by clone or other solutions. But I wonder why it worked before. What is the difference here?

An async block seems to be missing from your first code snippet. Did you mean this?

let h = tokio::spawn(async move {
    tracing::debug!("CLI interfacer starts");
    ...

If so, then the key difference is that move causes every variable mentioned in the async block to be moved into it, not borrowed by it. But you wrote your async fn to take a borrow, &Path, not an owned struct.

To fix this, you can either change the signature of serv_cli to take only owned values,

async fn serv_cli(uds: PathBuf, server: Server) -> Result<()> {

or wrap it in an async block that can own the things the function borrows (this is useful when the async function is in a library you didn't write):

let h = tokio::spawn(async move {
    serv_cli(&cli.uds, Server { ... }).await
});

Actually, no. The block returned a Future, so no async needed. And I did not have move from the beginning. That is why I am confused.

And yes, move fixed the issue. But why I do not need it before?

PS: I am using nightly rust.

If the block returned a Future, then everything else in the block was done before spawning the task, not within the spawned task. Thus, it is part of the parent task and can borrow things, unlike the spawned part.

Oh, I surely did not realize this subtle difference, the exact range to "be evaluated later". Thanks.

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.