How to use the Never type to return top level function inside async block

bail!() behaves differently inside an async block. How can I get the functionality that I want when inside async?

pub async fn member_update(member: &MemberUpdate, http: &HttpClient) -> Result<(), anyhow::Error> {
    let mut member_was_cached = true;

    let cached_member: GuildMember = MEMBERS_MEM_CACHE
        .get()
        .unwrap()
        .get_or_insert_with((member.guild_id.0, member.user.id.0), async {
            match query_db_for_member_or_return_current(&member).await {
                Ok(v) => {
                    if !v.1 { member_was_cached = false; };
                    v.0
                }
                Err(e) => bail!(e)
            }
        }
        )
        .await;
Ok(())
}

With what error does it fail?

This, at its core, is an API issue: that (async) .get_or_insert_with() does not allow for a "fallible thunk" (a Result-outputting Future).

One (ugly) option, but which does not require any extra API whatsoever, would be to yield a dummy "successful" value, and expressing the error through a captured flag which is accessible outside. This way you get to know that you have to bail:

    let mut errored = None;
    let cached_member: GuildMember = MEMBERS_MEM_CACHE
        .get()
        .unwrap()
        .get_or_insert_with((member.guild_id.0, member.user.id.0), async {
            match query_db_for_member_or_return_current(&member).await {
                Ok(v) => {
                    if !v.1 { member_was_cached = false; };
                    v.0
                }
                Err(e) => {
                    errored = Some(anyhow!(e));
                    produce_some_dummy_value()
                },
            }
        })
        .await
    ;
    if let Some(e) = errored { return Err(e); }
    /* … */
    Ok(())

A way cleaner option would be to have the MEMBERS_MEM_CACHE feature a .get_or_try_insert_with kind of API that would allow you to cleanly achieve what you seek.

In order to explore these better solutions, however, we need to know the exact API you are facing: what is the type of MEMBERS_MEM_CACHE? From which crate?

It's Moka-rs which does have a try variant of that function: Cache in moka::future - Rust

I tried to make that work before with this code:

let mut member_was_cached = true;

    let cached_member: GuildMember = MEMBERS_MEM_CACHE
        .get()
        .unwrap()
        .get_or_try_insert_with((member.guild_id.0, member.user.id.0), async {
            let v = query_db_for_member_or_return_current(&member).await?;
            if !v.1 { member_was_cached = false; };
            Ok(v.0)
        }
        )
        .await;

It seems to want a result, but when I give it a result then it wants GuildMember. Hmm. Here is the compiler error from this code:

error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/lib/member.rs:45:73
   |
44 |           .get_or_try_insert_with((member.guild_id.0, member.user.id.0), async {
   |  ______________________________________________________________________________-
45 | |             let v = query_db_for_member_or_return_current(&member).await?;
   | |                                                                         ^ cannot use the `?` operator in an async block that returns `GuildMember`
46 | |             if !v.1 { member_was_cached = false; };
47 | |             v.0
48 | |
49 | |         }
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Result<Infallible, anyhow::Error>>` is not implemented for `GuildMember`
   = note: required by `from_residual`

I found a solution:

let member_was_cached = Arc::new(AtomicBool::new(true));
    let mwc = Arc::clone(&member_was_cached);

    let cached_member = MEMBERS_MEM_CACHE.get().unwrap()
        .get_or_try_insert_with((member.guild_id.0, member.user.id.0), async move {
            match query_for_member_or_return_current(&member).await {
                Ok(v) => {
                    if !v.1 {
                        mwc.store(false, Ordering::Release)
                    };
                    Ok(v.0)
                }
                Err(e) => {
                    Err(e.into())
                }
            }
        })
        .await
        .map_err(|e| anyhow::anyhow!(e))?;

    if !member_was_cached.load(Ordering::Acquire) {
        return Ok(())
    }

Does:

        )
-       .await;
+       .await?;

fix the issue?

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.