async_std/io/
stdin.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Mutex;
4
5use crate::future;
6use crate::io::{self, Read};
7use crate::task::{spawn_blocking, Context, JoinHandle, Poll};
8use crate::utils::Context as _;
9
10/// Constructs a new handle to the standard input of the current process.
11///
12/// This function is an async version of [`std::io::stdin`].
13///
14/// [`std::io::stdin`]: https://doc.rust-lang.org/std/io/fn.stdin.html
15///
16/// ### Note: Windows Portability Consideration
17///
18/// When operating in a console, the Windows implementation of this stream does not support
19/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return
20/// an error.
21///
22/// # Examples
23///
24/// ```no_run
25/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
26/// #
27/// use async_std::io;
28///
29/// let stdin = io::stdin();
30/// let mut line = String::new();
31/// stdin.read_line(&mut line).await?;
32/// #
33/// # Ok(()) }) }
34/// ```
35pub fn stdin() -> Stdin {
36    Stdin(Mutex::new(State::Idle(Some(Inner {
37        stdin: std::io::stdin(),
38        line: String::new(),
39        buf: Vec::new(),
40        last_op: None,
41    }))))
42}
43
44/// A handle to the standard input of the current process.
45///
46/// This reader is created by the [`stdin`] function. See its documentation for
47/// more.
48///
49/// ### Note: Windows Portability Consideration
50///
51/// When operating in a console, the Windows implementation of this stream does not support
52/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return
53/// an error.
54///
55/// [`stdin`]: fn.stdin.html
56#[derive(Debug)]
57pub struct Stdin(Mutex<State>);
58
59/// The state of the asynchronous stdin.
60///
61/// The stdin can be either idle or busy performing an asynchronous operation.
62#[derive(Debug)]
63enum State {
64    /// The stdin is idle.
65    Idle(Option<Inner>),
66
67    /// The stdin is blocked on an asynchronous operation.
68    ///
69    /// Awaiting this operation will result in the new state of the stdin.
70    Busy(JoinHandle<State>),
71}
72
73/// Inner representation of the asynchronous stdin.
74#[derive(Debug)]
75struct Inner {
76    /// The blocking stdin handle.
77    stdin: std::io::Stdin,
78
79    /// The line buffer.
80    line: String,
81
82    /// The read buffer.
83    buf: Vec<u8>,
84
85    /// The result of the last asynchronous operation on the stdin.
86    last_op: Option<Operation>,
87}
88
89/// Possible results of an asynchronous operation on the stdin.
90#[derive(Debug)]
91enum Operation {
92    ReadLine(io::Result<usize>),
93    Read(io::Result<usize>),
94}
95
96impl Stdin {
97    /// Reads a line of input into the specified buffer.
98    ///
99    /// # Examples
100    ///
101    /// ```no_run
102    /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
103    /// #
104    /// use async_std::io;
105    ///
106    /// let stdin = io::stdin();
107    /// let mut line = String::new();
108    /// stdin.read_line(&mut line).await?;
109    /// #
110    /// # Ok(()) }) }
111    /// ```
112    pub async fn read_line(&self, buf: &mut String) -> io::Result<usize> {
113        future::poll_fn(|cx| {
114            let state = &mut *self.0.lock().unwrap();
115
116            loop {
117                match state {
118                    State::Idle(opt) => {
119                        let inner = opt.as_mut().unwrap();
120
121                        // Check if the operation has completed.
122                        if let Some(Operation::ReadLine(res)) = inner.last_op.take() {
123                            let n = res?;
124
125                            // Copy the read data into the buffer and return.
126                            buf.push_str(&inner.line);
127                            return Poll::Ready(Ok(n));
128                        } else {
129                            let mut inner = opt.take().unwrap();
130
131                            // Start the operation asynchronously.
132                            *state = State::Busy(spawn_blocking(move || {
133                                inner.line.clear();
134                                let res = inner.stdin.read_line(&mut inner.line);
135                                inner.last_op = Some(Operation::ReadLine(res));
136                                State::Idle(Some(inner))
137                            }));
138                        }
139                    }
140                    // Poll the asynchronous operation the stdin is currently blocked on.
141                    State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
142                }
143            }
144        })
145        .await
146        .context(|| String::from("could not read line on stdin"))
147    }
148}
149
150impl Read for Stdin {
151    fn poll_read(
152        self: Pin<&mut Self>,
153        cx: &mut Context<'_>,
154        buf: &mut [u8],
155    ) -> Poll<io::Result<usize>> {
156        let mut state_guard = self.0.lock().unwrap();
157        let state = &mut *state_guard;
158
159        loop {
160            match state {
161                State::Idle(opt) => {
162                    let inner = opt.as_mut().unwrap();
163
164                    // Check if the operation has completed.
165                    if let Some(Operation::Read(res)) = inner.last_op.take() {
166                        let n = res?;
167
168                        // If more data was read than fits into the buffer, let's retry the read
169                        // operation.
170                        if n <= buf.len() {
171                            // Copy the read data into the buffer and return.
172                            buf[..n].copy_from_slice(&inner.buf[..n]);
173                            return Poll::Ready(Ok(n));
174                        }
175                    } else {
176                        let mut inner = opt.take().unwrap();
177
178                        // Set the length of the inner buffer to the length of the provided buffer.
179                        if inner.buf.len() < buf.len() {
180                            inner.buf.reserve(buf.len() - inner.buf.len());
181                        }
182                        unsafe {
183                            inner.buf.set_len(buf.len());
184                        }
185
186                        // Start the operation asynchronously.
187                        *state = State::Busy(spawn_blocking(move || {
188                            let res = std::io::Read::read(&mut inner.stdin, &mut inner.buf);
189                            inner.last_op = Some(Operation::Read(res));
190                            State::Idle(Some(inner))
191                        }));
192                    }
193                }
194                // Poll the asynchronous operation the stdin is currently blocked on.
195                State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
196            }
197        }
198    }
199}
200
201cfg_unix! {
202    use crate::os::unix::io::{AsRawFd, RawFd};
203
204    impl AsRawFd for Stdin {
205        fn as_raw_fd(&self) -> RawFd {
206            std::io::stdin().as_raw_fd()
207        }
208    }
209
210    cfg_io_safety! {
211        use crate::os::unix::io::{AsFd, BorrowedFd};
212
213        impl AsFd for Stdin {
214            fn as_fd(&self) -> BorrowedFd<'_> {
215                unsafe {
216                    BorrowedFd::borrow_raw(std::io::stdin().as_raw_fd())
217                }
218            }
219        }
220    }
221}
222
223cfg_windows! {
224    use crate::os::windows::io::{AsRawHandle, RawHandle};
225
226    impl AsRawHandle for Stdin {
227        fn as_raw_handle(&self) -> RawHandle {
228            std::io::stdin().as_raw_handle()
229        }
230    }
231
232    cfg_io_safety! {
233        use crate::os::windows::io::{AsHandle, BorrowedHandle};
234
235        impl AsHandle for Stdin {
236            fn as_handle(&self) -> BorrowedHandle<'_> {
237                unsafe {
238                    BorrowedHandle::borrow_raw(std::io::stdin().as_raw_handle())
239                }
240            }
241        }
242    }
243}