rust 避免为同步traits及其异步对应项编写重复代码的最佳实践

qoefvg9y  于 2023-03-18  发布在  其他
关注(0)|答案(2)|浏览(175)

我在写一个扩展std::io::Writefutures::io::AsyncWrite(以及Read traits)的小型自定义协议时偶然发现了这个问题。我注意到写了很多重复的代码,因为无论是否异步,协议的行为都完全相同。这在详尽的测试中尤其糟糕,我使用了Cursor的两个版本,需要测试两个变体一起工作。
有没有一种方法可以连接这两种特性?也许一个宏可以通过省略.await和async部分(如果适用的话)来生成这两种变体。

参考代码实现

这是一个有点麻木下来。

impl<W> ProtoWriteExt for W
where
    W: Write,
{
    fn proto_write<T>(&mut self, value: &T) -> Result<(), ProtocolError>
    where
        T: Serialize,
    {
        // lots of code...

        // Only these calls change
        self.write_all(&bytes)?;

        // ...
    }
}

#[async_trait]
impl<W> ProtoAsyncWriteExt for W
where
    W: AsyncWrite + Unpin + Send + Sync,
{
    async fn proto_write<T>(&mut self, value: &T) -> Result<(), ProtocolError>
    where
        T: Serialize + Sync,
    {
        // Same code as above...

        // Only these calls change
        self.write_all(&bytes).await?;

        // ...
    }
}

参考测试

以后还会有很多这样的测试,我也要测试非阻塞版本和阻塞版本。

/// Writing a primitive value and then reading it results in an unchanged value.
#[test]
fn transfers_primitive_correctly() -> Result<(), ProtocolError> {
    let expected = 42;

    let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
    cursor.proto_write(&expected)?;
    cursor.set_position(0);
    let result: i32 = cursor.proto_read()?;

    assert_eq!(expected, result);

    Ok(())
}

/// Writing a primitive value and then reading it results in an unchanged value.
#[tokio::test]
async fn async_transfers_primitive_correctly() -> Result<(), ProtocolError> {
    let expected = 42;

    let mut cursor = futures::io::Cursor::new(Vec::<u8>::new());
    cursor.proto_write(&expected).await?;
    cursor.set_position(0);
    let result: i32 = cursor.proto_read().await?;

    assert_eq!(expected, result);

    Ok(())
}
lxkprmvk

lxkprmvk1#

reqwest crate在异步代码中完成了所有繁重的工作,而在blocking方法中只阻塞异步future,mongodbpostgres也是如此(基于tokio-postgres crate)。
有一个keyword generics initiative可以让你通用于async ness,但这只是早期阶段(另请参见the lang team meeting document)。
您可能可以通过宏来实现这一点,但这非常困难:同步版本将不得不调用同步方法,而异步版本将需要调用异步方法不知何故。我相信这是可能的,但我不知道一个板条箱提供。

aurhwmvo

aurhwmvo2#

duplicate crate可以创建包含目标替换的代码副本。
这在您的测试中可能特别有用,测试可能如下所示:

use duplicate::duplicate_item;

#[duplicate_item(
    test_attr     name                                  async   add_await(code);
    [test]        [transfers_primitive_correctly]       []      [code];
    [tokio::test] [async_transfers_primitive_correctly] [async] [code.await];
)]
#[test_attr]
async fn name() -> Result<(), ProtocolError> {
    let expected = 42;

    let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
    add_await([cursor.proto_write(&expected)])?;
    cursor.set_position(0);
    let result: i32 = add_await([cursor.proto_read()])?;

    assert_eq!(expected, result);

    Ok(())
}

这应该扩展到您所拥有的测试代码。
我很犹豫是否建议你用duplicate来代替impl,因为在我看来,它们太不一样了,不适合duplicate。我首先会探索重构代码的可能性,这样它们就可以简单地调用相同的函数。但是,如果这不可能,duplicate仍然可以使用。

相关问题