Skip to main content

dtact_util/io/
combinators.rs

1//! [`AsyncRead`]/[`AsyncWrite`] traits shared by every socket-like stream
2//! type in this crate (on both backends) plus the handful of generic
3//! combinators built on them. See the parent module's doc for what's
4//! deliberately not included and why.
5
6use std::future::Future;
7use std::io;
8
9/// Async byte-stream read, implemented by every socket-like stream type
10/// in this crate on both backends.
11///
12/// Exists purely so [`BufReader`]/[`copy`] below can be written once
13/// instead of duplicated per stream type per backend — not meant as a
14/// general-purpose extension point the way `tokio::io::AsyncRead` is.
15///
16/// `&self`, not `&mut self`: matches every implementor's own inherent
17/// `read` method, which already supports concurrent calls from different
18/// tasks via a shared reference. See this module's parent doc for why
19/// that means no `split()` is needed here.
20pub trait AsyncRead {
21    /// Read into `buf`, returning the number of bytes read (`0` = EOF).
22    ///
23    /// # Errors
24    /// Returns whatever the underlying stream's own `read` returns.
25    fn read(&self, buf: &mut [u8]) -> impl Future<Output = io::Result<usize>> + Send;
26}
27
28/// Async byte-stream write — the write-side twin of [`AsyncRead`]; see
29/// its doc for the shared rationale.
30pub trait AsyncWrite {
31    /// Write from `buf`, returning the number of bytes written.
32    ///
33    /// # Errors
34    /// Returns whatever the underlying stream's own `write` returns.
35    fn write(&self, buf: &[u8]) -> impl Future<Output = io::Result<usize>> + Send;
36}
37
38/// Copy every byte from `reader` to `writer` until `reader` reports EOF,
39/// returning the total byte count copied.
40///
41/// # Errors
42/// Returns whatever `reader.read`/`writer.write` returns, or
43/// [`io::ErrorKind::WriteZero`] if `writer.write` ever reports `0`
44/// written bytes for a nonempty buffer (mirrors `std::io::copy`'s own
45/// handling of a "stuck" writer).
46#[inline]
47pub async fn copy<R: AsyncRead + Sync + ?Sized, W: AsyncWrite + Sync + ?Sized>(
48    reader: &R,
49    writer: &W,
50) -> io::Result<u64> {
51    let mut buf = [0u8; 8192];
52    let mut total: u64 = 0;
53    loop {
54        let n = reader.read(&mut buf).await?;
55        if n == 0 {
56            return Ok(total);
57        }
58        let mut written = 0;
59        while written < n {
60            let w = writer.write(&buf[written..n]).await?;
61            if w == 0 {
62                return Err(io::Error::new(
63                    io::ErrorKind::WriteZero,
64                    "dtact-io: copy's writer reported 0 bytes written for a nonempty buffer",
65                ));
66            }
67            written += w;
68        }
69        total += written as u64;
70    }
71}
72
73/// Wraps an [`AsyncRead`] with an internal buffer, amortizing many small
74/// `.read()` calls into fewer, larger reads on the underlying stream.
75pub struct BufReader<T> {
76    inner: T,
77    buf: Box<[u8]>,
78    pos: usize,
79    filled: usize,
80}
81
82impl<T> BufReader<T> {
83    /// Wrap `inner` with an 8 KiB internal buffer.
84    pub fn new(inner: T) -> Self {
85        Self::with_capacity(8192, inner)
86    }
87
88    /// Wrap `inner` with a `capacity`-byte internal buffer.
89    #[must_use]
90    pub fn with_capacity(capacity: usize, inner: T) -> Self {
91        Self {
92            inner,
93            buf: vec![0u8; capacity.max(1)].into_boxed_slice(),
94            pos: 0,
95            filled: 0,
96        }
97    }
98
99    /// Borrow the wrapped stream.
100    pub const fn get_ref(&self) -> &T {
101        &self.inner
102    }
103
104    /// Unwrap back to the underlying stream. Any bytes already buffered
105    /// but not yet consumed via [`Self::read`] are discarded.
106    pub fn into_inner(self) -> T {
107        self.inner
108    }
109}
110
111impl<T: AsyncRead> BufReader<T> {
112    /// Read into `buf`, drawing from the internal buffer first and only
113    /// refilling it (via one `.read()` on the wrapped stream) once it's
114    /// fully drained.
115    ///
116    /// # Errors
117    /// Returns whatever the wrapped stream's `read` returns.
118    #[inline]
119    pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
120        if self.pos == self.filled {
121            let n = self.inner.read(&mut self.buf).await?;
122            self.pos = 0;
123            self.filled = n;
124            if n == 0 {
125                return Ok(0);
126            }
127        }
128        let available = &self.buf[self.pos..self.filled];
129        let n = available.len().min(buf.len());
130        buf[..n].copy_from_slice(&available[..n]);
131        self.pos += n;
132        Ok(n)
133    }
134}
135
136/// Wraps an [`AsyncWrite`] with an internal buffer, amortizing many small
137/// `.write()` calls into fewer, larger writes on the underlying stream.
138///
139/// **Does not flush on drop** (there's no async destructor to do it
140/// with) — call [`Self::flush`] explicitly before the writer goes out of
141/// scope, or buffered-but-unwritten bytes are silently lost.
142pub struct BufWriter<T> {
143    inner: T,
144    buf: Vec<u8>,
145    capacity: usize,
146}
147
148impl<T> BufWriter<T> {
149    /// Wrap `inner` with an 8 KiB internal buffer.
150    pub fn new(inner: T) -> Self {
151        Self::with_capacity(8192, inner)
152    }
153
154    /// Wrap `inner` with a `capacity`-byte internal buffer.
155    #[must_use]
156    pub fn with_capacity(capacity: usize, inner: T) -> Self {
157        let capacity = capacity.max(1);
158        Self {
159            inner,
160            buf: Vec::with_capacity(capacity),
161            capacity,
162        }
163    }
164
165    /// Borrow the wrapped stream.
166    pub const fn get_ref(&self) -> &T {
167        &self.inner
168    }
169
170    /// Unwrap back to the underlying stream. Any buffered bytes not yet
171    /// flushed via [`Self::flush`] are silently discarded — see this
172    /// type's doc.
173    pub fn into_inner(self) -> T {
174        self.inner
175    }
176}
177
178impl<T: AsyncWrite> BufWriter<T> {
179    /// Buffer `data`, flushing the internal buffer first if `data`
180    /// wouldn't fit, and bypassing the buffer entirely (writing straight
181    /// through) for a chunk at least as large as the buffer's own
182    /// capacity — buffering it first would just be an extra copy.
183    ///
184    /// # Errors
185    /// Returns whatever an internal [`Self::flush`] or the wrapped
186    /// stream's `write` returns.
187    #[inline]
188    pub async fn write(&mut self, data: &[u8]) -> io::Result<usize> {
189        if data.len() >= self.capacity {
190            self.flush().await?;
191            return self.inner.write(data).await;
192        }
193        if self.buf.len() + data.len() > self.capacity {
194            self.flush().await?;
195        }
196        self.buf.extend_from_slice(data);
197        Ok(data.len())
198    }
199
200    /// Write out every buffered byte, retrying a short write until the
201    /// whole buffer is flushed.
202    ///
203    /// # Errors
204    /// Returns whatever the wrapped stream's `write` returns, or
205    /// [`io::ErrorKind::WriteZero`] if it ever reports `0` written bytes
206    /// for a nonempty buffer.
207    #[inline(always)]
208    pub async fn flush(&mut self) -> io::Result<()> {
209        let mut written = 0;
210        while written < self.buf.len() {
211            let n = self.inner.write(&self.buf[written..]).await?;
212            if n == 0 {
213                return Err(io::Error::new(
214                    io::ErrorKind::WriteZero,
215                    "dtact-io: BufWriter's wrapped stream reported 0 bytes written",
216                ));
217            }
218            written += n;
219        }
220        self.buf.clear();
221        Ok(())
222    }
223}