Skip to main content

tokio_util/io/
std_adapter.rs

1use pin_project_lite::pin_project;
2use std::future::Future;
3use std::marker::PhantomData;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use tokio::io::AsyncWrite;
7
8/// Adapts an implementor of [`AsyncWrite`] to be usable in some context requiring [`std::io::Write`]
9///
10/// **Important**: this adapter is limited to a specific scenario where the calling code supports
11/// restarting. In particular, the calling code MUST NOT call the `write_all` method on the writer.
12#[derive(Debug)]
13pub struct StdWriteAdapter<'a, 'b, W> {
14    writer: W,
15    cx: &'a mut Context<'b>,
16}
17
18impl<'a, 'b, W: AsyncWrite> StdWriteAdapter<'a, 'b, W> {
19    /// Constructs the adapter.
20    ///
21    /// This method produces a restricted implementation of [`std::io::Write`] and may only be
22    /// passed to functions that properly implement restarting and don't block. This property is
23    /// neither checked by the type system nor required by the `Write` trait and thus it has to be
24    /// explicitly guaranteed by each function that takes the returned value as argument.
25    pub fn new_assuming_correct_usage(writer: W, cx: &'a mut Context<'b>) -> Self {
26        Self {
27            writer,
28            cx,
29        }
30    }
31}
32
33impl<'a, 'b, W: AsyncWrite + Unpin> std::io::Write for StdWriteAdapter<'a, 'b, W> {
34    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
35        match Pin::new(&mut self.writer).poll_write(self.cx, buf) {
36            Poll::Ready(result) => result,
37            Poll::Pending => Err(std::io::ErrorKind::WouldBlock.into()),
38        }
39    }
40
41    /// Calling this method would be wrong therefore it always panics.
42    #[track_caller]
43    fn write_all(&mut self, _: &[u8]) -> std::io::Result<()> {
44        panic!("operation not supported by the adapter")
45    }
46
47    fn flush(&mut self) -> std::io::Result<()> {
48        match Pin::new(&mut self.writer).poll_flush(self.cx) {
49            Poll::Ready(result) => result,
50            Poll::Pending => Err(std::io::ErrorKind::WouldBlock.into()),
51        }
52    }
53}
54
55pin_project! {
56    /// An asynchronous operation implemented using a restricted function taking `std::io::Write`.
57    ///
58    /// This is a [`Future`] that calls `F` each time it's polled, passing in a restricted writer.
59    /// The closure `F` may pass this writer to other functions which require `std::io::Write` as
60    /// long as those functions guarantee correct restarting in the presence of `WouldBlock`
61    /// errors.
62    ///
63    /// E.g. given `fn perform_single_write(&mut self, writer: impl Write) -> io::Result<()>` which
64    /// guarantees to keep consistent state in `self` even if `writer` returns error, one might
65    /// write code like this:
66    ///
67    /// ```no-compile
68    /// StdWriteAdapterFuture::new_assuming_correct_usage(writer, |writer| {
69    ///     if self.needs_write() {
70    ///         match self.perform_single_write() {
71    ///             Ok(()) => (),
72    ///             Err(error) if error.kind() == ErrorKind::WouldBlock => {
73    ///                 Poll::Pending
74    ///             }
75    ///             Err(error) => Poll::Ready(Err(error)),
76    ///         }
77    ///     } else {
78    ///         Poll::Ready(Ok(()))
79    ///     }
80    /// })
81    /// ```
82    pub struct StdWriteAdapterFuture<W, T, F> {
83        #[pin]
84        writer: W,
85        f: F,
86        _phantom: PhantomData<fn() -> T>,
87    }
88}
89
90impl<W: AsyncWrite, T, F: FnMut(StdWriteAdapter<'_, '_, Pin<&mut W>>) -> Poll<T>> StdWriteAdapterFuture<W, T, F> {
91    /// Constructs the future.
92    ///
93    /// It is required that the closure `f` only passes the writer given to it to functions that
94    /// guarantee correct restarting in case of `WouldBlock` errors. In particular, the `write_all`
95    /// method on the writer MUST NOT be called (it will panic) but also any other code that
96    /// behaves similarly by keeping the state on the stack and losing it if error happens (easy to
97    /// do by accident unless specific care was taken to avoid it).
98    pub fn new_assuming_correct_usage(writer: W, f: F) -> Self {
99        Self {
100            writer,
101            f,
102            _phantom: PhantomData,
103        }
104    }
105}
106
107impl<W: AsyncWrite, T, F: FnMut(StdWriteAdapter<'_, '_, Pin<&mut W>>) -> Poll<T>> Future for StdWriteAdapterFuture<W, T, F> {
108    type Output = T;
109
110    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
111        let this = self.project();
112        (this.f)(StdWriteAdapter::new_assuming_correct_usage(this.writer, cx))
113    }
114}