12 releases (4 breaking)
0.5.0 | Jun 23, 2025 |
---|---|
0.4.1 | Jun 22, 2025 |
0.3.3 | Jun 21, 2025 |
0.2.2 | May 29, 2025 |
0.1.1 | May 28, 2025 |
#322 in Rust patterns
6,200 downloads per month
Used in 12 crates
(3 directly)
40KB
622 lines
derive-io
A Rust crate that provides derive macros for implementing sync and async I/O traits on structs and enums (including Tokio, stdlib I/O, and more).
Supported traits
#[derive(Read)]
:std::io::Read
#[derive(BufRead)]
:std:io::BufRead
#[derive(Write)]
:std::io::Write
#[derive(AsyncRead)]
:tokio::io::AsyncRead
#[derive(AsyncWrite)]
:tokio::io::AsyncWrite
#[derive(AsFileDescriptor)]
:std::os::fd::{AsFd, AsRawFd}
std::os::windows::io::{AsHandle, AsRawHandle}
#[derive(AsSocketDescriptor)]
:std::os::fd::{AsFd, AsRawFd}
std::os::windows::io::{AsSocket, AsRawSocket}
Features
- Derive most common I/O traits for structs and enums
- Support for both named and tuple structs
- Support for enums with multiple variants
- Support for split read/write streams (ie: two fields provide the read/write halves)
- Support for generic types
- Support for duck typing (ie: implementing traits using a method with a "similar" interface)
- Individual methods can be overridden with custom implementations
- Support for
as_ref
orderef
attribute on fields to delegate to the inner type- Note: for traits requiring a pinned-self (ie: async read/write), the holder
type and the outer type must both be
Unpin
!
- Note: for traits requiring a pinned-self (ie: async read/write), the holder
type and the outer type must both be
- Pin safety: internal pin projection never allows a
&mut
to escape, thus upholding anyPin
guarantees.
as_ref
/deref
delegation
Most I/O traits are implemented correctly for Box<dyn (trait)>
(that is: they
are implemented for Box<T> where T: ?Sized
). However, some traits have
accidental or intentional additional Sized
requirements which prevent
automatic delegation from working. Generally this is only required for
AsFileDescriptor
and AsSocketDescriptor
, as most other traits are
implemented for themselves on Box<T> where T: Trait + ?Sized
.
To uphold Pin
safety guarantees, both the inner and outer types must be
Unpin
.
The as_ref
attribute can be used to delegate to the inner type's unwrapped
type as_ref
/as_mut
implementation. The deref
attribute can be used to
delegate to the inner type's pointee via Deref
/DerefMut
.
use derive_io::{AsyncRead, AsyncWrite, AsFileDescriptor};
#[cfg(unix)]
trait MyStream: tokio::io::AsyncRead + tokio::io::AsyncWrite
+ std::os::fd::AsFd + std::os::fd::AsRawFd + Unpin {}
#[cfg(windows)]
trait MyStream: tokio::io::AsyncRead + tokio::io::AsyncWrite
+ std::os::windows::io::AsHandle + std::os::windows::io::AsRawHandle + Unpin {}
#[derive(AsyncRead, AsyncWrite, AsFileDescriptor)]
pub struct DelegateAsRef {
#[read]
#[write]
// This won't work with #[descriptor] because `AsRawFd` is not implemented for
// `Box<dyn AsRawFd>`.
#[descriptor(as_ref)]
stream: Box<dyn MyStream>,
}
#[derive(AsyncRead, AsyncWrite, AsFileDescriptor)]
pub struct DelegateDeref {
#[read]
#[write]
// This won't work with #[descriptor] because `AsRawFd` is not implemented for
// `Box<dyn AsRawFd>`. This won't work with #[descriptor(as_ref)] because
// `as_ref` and `as_mut` on a `Pin` gives you a `Box`.
#[descriptor(deref)]
stream: std::pin::Pin<Box<dyn MyStream>>,
}
Overrides
#[read(<function>=<override>)]
and #[write(<function>=<override>)]
may be
specified to redirect a method to a custom implementation.
duck
delegation
duck
delegation uses non-trait impl
methods defined on a type to implement
the trait (i.e. "duck typing"). This is useful for when you want to implement a
trait for a type that doesn't implement the trait directly, but has methods that
are similar to the trait
#[read(duck)]
and #[write(duck)]
may be specified on the outer type or an
inner field.
When using duck
delegation, specify the methods to delegate to using the
#[duck(...)]
attribute:
use derive_io::{AsyncRead, AsyncWrite};
use std::task::{Context, Poll};
#[derive(AsyncRead, AsyncWrite)]
#[duck(poll_read, poll_write, poll_flush, poll_shutdown, poll_write_vectored, is_write_vectored)]
#[read(duck)]
#[write(duck)]
pub struct DuckType {
inner: tokio::net::TcpStream,
}
impl DuckType {
pub fn poll_read(
&mut self,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
todo!()
}
// ... poll_write, poll_flush, poll_shutdown, poll_write_vectored, is_write_vectored, etc
}
Examples
Tokio
use tokio::net::*;
use derive_io::{AsyncRead, AsyncWrite, AsSocketDescriptor};
#[derive(AsyncRead, AsyncWrite, AsSocketDescriptor)]
pub enum TokioStreams {
Tcp(#[read] #[write] #[descriptor] TcpStream),
#[cfg(unix)]
Unix(#[read] #[write] #[descriptor] UnixStream),
Split{
#[read] #[descriptor(as_ref)] read: tokio::net::tcp::OwnedReadHalf,
#[write] write: tokio::net::tcp::OwnedWriteHalf,
},
}
Generic types are supported. The generated implementations will automatically
add a where
clause to the impl block for each stream type.
use derive_io::{AsyncRead, AsyncWrite};
#[derive(AsyncRead, AsyncWrite)]
pub struct Generic<S> { // where S: AsyncRead + AsyncWrite
#[read]
#[write]
stream: S,
}
Override one method in the write implementation:
use derive_io::{AsyncRead, AsyncWrite};
#[derive(AsyncRead, AsyncWrite)]
pub struct Override {
#[read]
#[write(poll_write=override_poll_write)]
stream: tokio::net::TcpStream,
}
pub fn override_poll_write(
stm: std::pin::Pin<&mut tokio::net::TcpStream>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> {
todo!()
}
Dependencies
~0–7.5MB
~56K SLoC