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