Approach for wrapping async code with function calls?

I've got some code that tracks its execution state in a DB, i.e. tracking when a task has been started, completed, or failed.

I'm trying to find a good approach for reducing boilerplace/repeated code for doing this.

I could probably use a macro, but wondering if there are any other good approaches that I'm missing out on.

I wondered if I could just use a wrapper function, but couldn't figure out the right generic approach for creating a function that takes any async function as an argument.

I've got something like:

// Some DB client that updates task status by name.
let db = get_db_client();

// Mark a task as started, then run the task itself.
db.start_task("foo");
let data = foo_task()
    .await
    .map(|v| {
        db.complete_task("foo");
        v
    })
    .map_err(|err| {
        db.fail_task("foo", err.to_string());
        err
    })?;

db.start_task("bar");
let data2 = bar_task(1, 2, 3)
    .await
    .map(|v| {
        db.complete_task("bar");
        v
    })
    .map_err(|err| {
        db.fail_task("bar", err.to_string());
        err
    })?;

// ...etc. ...

Any suggestions/pointers?

You can't express "any function" in current Rust (that'd likely require variadic generics or some other feature that allows you to leave the number of arguments unspecified).

Instead, you could just call the async function and pass the resulting Future directly to your wrapper (untested):

async fn wrap<T, E, F>(db: &DbClient, name: &str, future: F) -> Result<T, E>
where
    F: Future<Output = Result<T, E>>
{
    db.start_task(name);
    let data = future
        .await
        .map(|v| {
            db.complete_task(name);
            v
        })
        .map_err(|err| {
            db.fail_task(name, err.to_string());
            err
        })
}
1 Like