Method that takes an async closure with self as argument

Hi,

I am trying to create an async version from embedded-sdmmc-rs: GitHub - Murmele/embedded-sdmmc-rs at async, but when I try to compile I get the error message:

lifetime may not live long enough
   --> src/sdcard/mod.rs:298:35
    |
298 |           self.with_chip_select(|s| async move {
    |  ________________________________--_^
    | |                                ||
    | |                                |return type of closure `{async block@src/sdcard/mod.rs:298:35: 329:10}` contains a lifetime `'2`
    | |                                has type `&'1 mut SdCardInner<SPI, CS, DELAYER>`
299 | |             if blocks.len() == 1 {
300 | |                 // Start a single-block write
301 | |                 s.card_command(CMD24, start_idx).await?;
...   |
328 | |             Ok(())
329 | |         }).await
    | |_________^ returning this value requires that `'1` must outlive `'2`

I found also a similar post, but I did not understand it completely: Function that takes an async closure
The difference is that I am passing the closure to a function of self, and therefore the requirement of the livetime should be fullfilled or am I wrong here?

I tried to reproduce the problem in a Playground. But the error message is not exactly the same.

async fn write<'a>(&'a mut self, blocks: &[Block], start_block_idx: BlockIdx) -> Result<(), Error> {
        let start_idx = match self.card_type {
            Some(CardType::SD1 | CardType::SD2) => start_block_idx.0 * 512,
            Some(CardType::SDHC) => start_block_idx.0,
            None => return Err(Error::CardNotFound),
        };
        self.with_chip_select(|s| async move {
            if blocks.len() == 1 {
                // Start a single-block write
                s.card_command(CMD24, start_idx).await?;
                s.write_data(DATA_START_BLOCK, &blocks[0].contents).await?;
                s.wait_not_busy(Delay::new_write()).await?;
                if s.card_command(CMD13, 0).await? != 0x00 {
                    return Err(Error::WriteError);
                }
                if s.read_byte().await? != 0x00 {
                    return Err(Error::WriteError);
                }
            } else {
                // > It is recommended using this command preceding CMD25, some of the cards will be faster for Multiple
                // > Write Blocks operation. Note that the host should send ACMD23 just before WRITE command if the host
                // > wants to use the pre-erased feature
                s.card_acmd(ACMD23, blocks.len() as u32).await?;
                // wait for card to be ready before sending the next command
                s.wait_not_busy(Delay::new_write()).await?;

                // Start a multi-block write
                s.card_command(CMD25, start_idx).await?;
                for block in blocks.iter() {
                    s.wait_not_busy(Delay::new_write()).await?;
                    s.write_data(WRITE_MULTIPLE_TOKEN, &block.contents).await?;
                }
                // Stop the write
                s.wait_not_busy(Delay::new_write()).await?;
                s.write_byte(STOP_TRAN_TOKEN).await?;
            }
            Ok(())
        }).await
    }

The error is different because you forgot move. The answer is pretty simple though. You need a lifetime.

async fn takes_closure<'a, F, Fut>(&'a mut self, f: F) -> Result<(), ()>
where
    F: FnOnce(&'a mut Self) -> Fut,
    Fut: Future<Output = Result<(), ()>> + 'a,
{
    f(self).await
}

Without the lifetime, you can only pass closures that return a future that does not capture the &mut Self value.

1 Like

Thanks for the hint, unfortunately the playground did not represent the correct structure and therefore there it works. I updated the Playground to get the same error message as below: Rust Playground

async fn with_chip_select<'a, Fut, F, T>(&'a mut self, func: F) -> Result<T, Error>
    where
        F: FnOnce(&'a mut Self) -> Fut,
        Fut: Future<Output = Result<T, Error>> + 'a
    {
        self.cs_low()?;
        let result = func(self).await;
        self.cs_high()?;
        result
    }
error[E0499]: cannot borrow `*self` as mutable more than once at a time
   --> src/sdcard/mod.rs:569:9
    |
562 |     async fn with_chip_select<'a, Fut, F, T>(&'a mut self, func: F) -> Result<T, Error>
    |                               -- lifetime `'a` defined here
...
568 |         let result = func(self).await;
    |                      ----------
    |                      |    |
    |                      |    first mutable borrow occurs here
    |                      argument requires that `*self` is borrowed for `'a`
569 |         self.cs_high()?;
    |         ^^^^ second mutable borrow occurs here

I understand the problem now here: result has lifetime 'a , but before returning result, self.cs_high() will be called, but 'a did not end yet

To perhaps clarify a bit more, this bound requires a closure that takes any lifetime (including those the caller cannot name, because they're borrows local to the function body) and returns the same future for any input lifetime (and thus cannot capture the input lifetime):

async fn takes_closure<F, Fut>(&mut self, f: F) -> Result<(), ()>
    where F: FnOnce(&mut Self) -> Fut,
    // Same bound:
    for<'any> F: FnOnce(&'any mut Self) -> Fut,

Whereas this bound requires a closure that takes a single lifetime which the caller chooses:

async fn takes_closure<'a, F, Fut>(&'a mut self, f: F) -> Result<(), ()>
    where F: FnOnce(&'a mut Self) -> Fut,

It's ok that the caller chooses the lifetime in the original playground, since they're also supplying the argument you pass in and that's all you do. It fails in the second playground, because you need the &mut you pass in to the closure to be a reborrow that's shorter than the function body in order for self to not be exclusively borrowed for the entirety of the function body (for all of the caller-chosen lifetime 'a).

For that you do need the higher-ranked (for<'any> FnOnce(&mut Self)...) version, but that means you need to change the bound to not mention the future type as a type variable (Fut); see here for more explanation on why.

Unfortunately in this case, the closure isn't 'static, (edit: see below) and one starts running into language limitations around higher-ranked bounds, complicated by the unnameable types, when attempting Solution 3. Sometimes there's still a way, but I don't have time to pour into an attempt this instant.

1 Like

Whoops, that's not it. It's Rust's poor higher-ranked closure inference (exasperated by the inability to name opaque types). You may need type erasure to get around this one.

"Solution 3" still works with async fns (which cannot capture, but sidestep Rust's poor higher-ranked closure inference.)

1 Like

Thanks for the solutions. I have to check this, because Box is not available without std. Or in general is there a better way to solve this problem? In another fork they just copied the code of the closure directly in the read function and added a cs_low() before and a cs_hight() afterwards. With this closure I am sure that cs will be pulled high, and I don't have to think about all the time.

I've learned that the correct way to mention the lifetime here is actually this. :wink:

Reference: https://www.youtube.com/watch?v=CWiz_RtA1Hw&t=815s

I don't know of a better way than using async fn instead (if possible). I'm also not sure what the no-alloc alternative to Box would be, if there is one.

In that example (which is not a solution for the OP), where the function is not higher ranked, the type parameter Fut can implicitly capture 'a and no + 'a or Captures<&'a ()> is needed at all.

1 Like

Oops! You're right. Nevertheless the + 'a or Captures<&'a () thing is a pretty nasty nuance.^^

1 Like

Thanks all, for now I implemented it in the way that I explicit set cs_low and high and between I have an inner function which is doing the rest:

self.cs_low()?;
let result = self.some_function_inner(...).await;
self.cs_high()?;
return result;

In this case I have to be really carefully, but it works easily :slight_smile: