Macro to DRY sync and async code?

Suppose that I want to write two functions,

use futures::AsyncRead;
use std::io::Read;

async fn decode_variable_async<R: AsyncRead + Unpin + Send>(reader: &mut R) -> Result<u64>;
fn decode_variable<R: Read>(reader: &mut R) -> Result<u64>;

that read some bytes and perform some decoding (e.g. zigzag). They are semantically equal apart from how we call reader.read_exact([u8; 1])? vs reader.read_exact([u8; 1]).await?.

E.g. the sync version reads:

fn decode_variable<R: Read>(reader: &mut R) -> Result<u64> {
    let mut i = 0u64;
    let mut buf = [0u8; 1];

    let mut j = 0;
    loop {
        if j > 9 {
            // if j * 7 > 64
            panic!() // todo move to an error
        }
        reader.read_exact(&mut buf[..])?;
        i |= (u64::from(buf[0] & 0x7F)) << (j * 7);
        if (buf[0] >> 7) == 0 {
            break;
        } else {
            j += 1;
        }
    }

    Ok(i)
}

Is there a macro that I can use to encapsulate the same semantics above without copying the whole algorithm? E.g. something like

async fn decode_variable_async<R: AsyncRead + Unpin + Send>(reader: &mut R) -> Result<u64> {
    decode!(.await?)
}

async fn decode_variable<R: Read>(reader: &mut R) -> Result<u64> {
    decode!(?)
}

I also tried

decode!(AsyncReadExt::read_exact(reader, &buf[..]).async?)
decode!(Read::read_exact(reader, &buf)?)

but I am being unable to achieve this.

The only problem with this is that you’ll need to pass the variable name reader, otherwise the code inside the macro can’t reference that variable due to hygiene rules.

macro_rules! decode {
    ($reader:ident $($_await:tt)*) => {
        {
            let mut i = 0u64;
            let mut buf = [0u8; 1];
        
            let mut j = 0;
            loop {
                if j > 9 {
                    // if j * 7 > 64
                    panic!() // todo move to an error
                }
                $reader.read_exact(&mut buf[..])$($_await)*?;
                i |= (u64::from(buf[0] & 0x7F)) << (j * 7);
                if (buf[0] >> 7) == 0 {
                    break;
                } else {
                    j += 1;
                }
            }
        
            Ok(i)
        }
    }
}

async fn decode_variable_async<R: AsyncRead + Unpin + Send>(reader: &mut R) -> Result<u64> {
    decode!(reader .await)
}

async fn decode_variable<R: Read>(reader: &mut R) -> Result<u64> {
    decode!(reader)
}
1 Like

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.