避免编写同步性状的重复代码及其异步方的最佳实践

发布于 2025-02-09 02:18:51 字数 2061 浏览 3 评论 0 原文

我在编写一个小型自定义协议时偶然发现了这个问题,该协议同时扩展了 std :: io :: write 期货:: io :: io :: asyncwrite (以及读取特征)。我注意到编写了很多重复的代码,因为该协议的行为是否完全相同,无论它是否是异步。这在详尽的测试中尤其不错,在详尽的测试中,我使用两个版本的光标,并且需要测试这两个变体一起工作。

有没有办法弥合两种特征?也许是一个宏,它仅通过遗漏了.await和异步部件(如果适用)来生成两个变体。

参考代码实现

这有点麻烦。

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(())
}

I stumbled upon this issue while writing a small custom protocol that extends both the std::io::Write and futures::io::AsyncWrite (as well as the Read traits). I noticed writing a lot of duplicate code, as the protocol behaves exactly the same whether it's async or not. This is especially bad within the exhaustive tests, where i use both versions of Cursor and need to test both variants working together.

Is there a way to bridge both kinds of traits? Maybe a macro that generates both variants by just leaving out the .await and async parts (if applicable).

Reference code implementation

This is a bit numbed down.

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?;

        // ...
    }
}

Reference tests

There are gonna be a lot more tests like this and i would have to test the nonblocking version against the blocking one as well.


/// 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(())
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

琉璃梦幻 2025-02-16 02:18:51

reqwest 板条板在异步代码中进行所有繁重的举重,并且在阻止方法仅在异步期货上阻止。所以 and /code> (基于 tokio-postgres 板条箱)。

有一个 keyword generic instirive ,这将使您可以通过 async ness,但仅​​是早期阶段(另请参见 lang Team会议文档)。

您可能可以通过宏来做到这一点,但这很难:同步版本必须调用同步方法,而异步版本将需要以某种方式调用异步方法。我相信这是可能的,但是我不知道提供的板条箱。

The reqwest crate does all heavy lifting in async code, and within the blocking methods just blocks on the async futures. So do mongodb and postgres (based on the tokio-postgres crate).

There is a keyword generics initiative that will allow you to be generic over asyncness, but it is only early stages (see also the lang team meeting document).

You probably can do that by a macro, but it is pretty hard: the sync version will have to call sync methods while the async version will need to call async methods somehow. I believe this is possible, however I don't know a crate that provides that.

像你 2025-02-16 02:18:51

重复 板条箱可以用目标替代品创建代码的重复项。

这在您的测试中可能特别有用,这可能是这样的:

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(())
}

这应该扩展到您拥有的测试代码。

我不愿意建议您使用重复为您的 Inpland 的s,因为在我看来它们太不同了,无法为重复< /代码>。
我首先要探索重构代码的可能性,以便它们只是调用相同的功能。
但是,如果不可能,仍然可以使用重复

The duplicate crate can create duplicates of code with targeted substitutions in it.

This could be especially useful in your tests, which could look like this:

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(())
}

This should expand to the test code you have.

I'm hesitant to advise you to use duplicate for your impl's as it seems to me that they are too different to be a good case for duplicate.
I would first explore the possibility of refactoring the code, such that they simply call the same function.
However, if that is not possible, duplicate could still be used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文