Rust - Using map with mutable reference and async; possibly using Stream?

Viewed 37

Is there a way with Rust to perform the following operation without making models mutable? Possibly by using Stream? The core issue with using uuids.iter().map(...) appears to be (a) passing/moving &mut conn into the closure and (b) the fact that DatabaseModel::load is async.

// assume:
// uuid: Vec<uuid::Uuid>
// conn: &mut PgConnection from `sqlx`

let mut models = Vec::<DatabaseModel>::new();
for uuid in &uuids {
    let model = DatabaseModel::load(conn, uuid).await;
    models.extend(model);
}
//.. do immutable stuff with `models`

A more basic toy example without (a) and (b) above may look like the following, which is closer to what I wish for:

let models = uuids.iter().map(|uuid| DatabaseModel::load(uuid));
1 Answers

Yes, what you're looking for is a Stream, a.k.a. "an asynchronous version of Iterator".

You can adapt an existing iterator into a stream by using futures::stream::iter and chain that with .then() to call an async function for each element. Here's an example (playground):

use futures::StreamExt;

let models: Vec<_> = futures::stream::iter(&uuids)
    .then(|uuid| DatabaseModel::load(conn, uuid))
    .collect()
    .await;

However, this won't work if conn is a mutable reference. Streams can't ensure that their futures run purely sequentially. Through the stream its possible to create multiple futures, which would all need the &mut Connection to make progress, but that is not allowed. You would need some form of interior mutability, likely an asynchronous Mutex, to ensure use of conn is exclusive (playground):

use futures::StreamExt;
use tokio::sync::Mutex;

let conn = Mutex::new(conn);
let models: Vec<_> = futures::stream::iter(&uuids)
    .then(|uuid| async {
        let mut conn = conn.lock().await;
        DatabaseModel::load(&mut conn, uuid).await
    })
    .collect()
    .await;

If that is unsavory, then you do need a for-loop with .await to ensure that uses of conn are exclusive. Since that is pretty much set in stone, any other method to create models without mutating it would simply be obtuse.

Related