My Rust code doesn't compile, I don't know what to do

Originally asked here.

I apparently hit a Rust bug (they say, it’s not a bug, but at least the error message is too confusing) and have no idea what to do. The minimal example:

use std::cell::RefCell;

thread_local! {
    static REQUESTS_CHECKER: RefCell<()> = RefCell::new(());
}

async fn a() {}

async fn b() {
    REQUESTS_CHECKER.with_borrow_mut(async |c|
        a().await
    ).await
}

fn main() {}

The real code:


thread_local! {
    static requests_checker: RefCell<HttpRequestsChecker> = RefCell::new(HttpRequestsChecker::new());
}

// ...

#[update]
async fn call_http(
    request: SharedWrappedHttpRequest,
    params: HttpRequestParams,
    config_id: String,
) -> HttpResponsePayload {
    requests_checker.with_borrow_mut(async |c|
        c.checked_http_request_wrapped(
            request,
            Some(TransformContext {
                function: TransformFunc(candid::Func{principal: canister_self(), method: "transform".to_string()}),
                context: Vec::new(),
            }),
            params,
            config_id,
        ).await
    ).await.unwrap()
}
mismatched types
expected `async` closure body `{async closure body@canister/src/lib.rs:27:9: 35:16}`
   found `async` closure body `{async closure body@canister/src/lib.rs:27:9: 35:16}`
no two async blocks, even if identical, have the same type
consider pinning your async block and casting it to a trait object

Help me to find a bug workaround.

I’ve found a soluiton:

#![feature(thread_local)]
use std::cell::RefCell;
// ...
use static_init::{dynamic};
// ...
// TODO: Save/restore it on upgrade.
#[dynamic]
#[thread_local]
static REQUESTS_CHECKER: RefCell<HttpRequestsChecker> = RefCell::new(HttpRequestsChecker::new());
// ...
    let mut c: std::cell::RefMut<'_, HttpRequestsChecker> = REQUESTS_CHECKER.borrow_mut();
    c.checked_http_request_wrapped(
        request,
        Some(TransformContext {
            function: TransformFunc(candid::Func{principal: canister_self(), method: "transform".to_string()}),
            context: Vec::new(),
        }),
        params,
        config_id,
    ).await.unwrap()

You can’t use async closures in with_borrow_mut. Nor should you; other incoming calls can occur during awaits, and you wouldn’t want them to panic with BorrowMutError. Your alternative version still has this possibility. Clippy should generally catch this with the clippy::await_holding_refcell_ref lint. The best solution is to put the refcell inside HttpRequestsChecker, so that it can lock and unlock the resource exactly when needed. This is a general pattern; a lock-safe service object should generally manage its own locks.