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}