Benoît Faucon
Benoît Faucon

Reputation: 278

Idiomatic way to call an async Rust function that takes a reference, when I'd want to pass ownership to the function

There's a pattern I'm frequently encountering when writing async Rust code. Let's say I have a Foo struct (in real code, this could be something like a network client) and an async function that takes a reference to an instance of this struct:

struct Foo {
    i: i32
}

async fn use_foo(foo: &Foo) -> () {
    // do something with foo
}

Then, I want to run several use_foo concurrently, each with its own reference to a different Foo. A natural (but incorrect) way to do this would be:

use futures::stream::FuturesUnordered;

async fn foo_in_a_loop() {
    let futures = FuturesUnordered::new();
    for i in 0..10 {
        let foo = Foo { i };
        // does not compile: `foo` is dropped before the future is dropped
        futures.push(use_foo(&foo));
    }
}

which doesn't compile:

error[E0597]: `foo` does not live long enough
  --> src/main.rs:53:30
   |
53 |         futures.push(use_foo(&foo));
   |                              ^^^^ borrowed value does not live long enough
54 |     }
   |     - `foo` dropped here while still borrowed
55 | }
   | - borrow might be used here, when `futures` is dropped and runs the `Drop` code for type `FuturesUnordered`
   |
   = note: values in a scope are dropped in the opposite order they are defined

For more information about this error, try `rustc --explain E0597`

My question boils down to: what's the most idiomatic way to fix this error? I'd like to "force" use_foo to take ownership of the Foo instance.

Right now, I'm using a helper function that takes ownership of foo:

async fn use_foo_owned(foo: Foo) -> () {
    use_foo(&foo).await
}

async fn foo_in_a_loop_owned() {
    let futures = FuturesUnordered::new();
    for i in 0..10 {
        let foo = Foo { i };
        futures.push(use_foo_owned(foo));
    }
}

which does compile, but introducing an extra function just to please the borrow checker is clunky.

Note that sometimes use_foo may come from another crate and I may not be able to change its signature, but if there's an elegant solution that involves modifying use_foo, I'm interested too.

Upvotes: 4

Views: 902

Answers (1)

kmdreko
kmdreko

Reputation: 59827

Use an async move {} block to create a future that takes ownership of foo and calls use_foo:

futures.push(async move { use_foo(&foo).await });

Upvotes: 9

Related Questions