Description
Feature gate: #![feature(stdin_forwarders)]
This is a tracking issue for adding new methods Stdin::lines
and that will forward to the corresponding methods on the Stdin::split
BufRead
implementation of StdinLock
. This proposal is related to #86845, and further reduces the obstacles for beginners to write simple interactive terminal programs.
Especially for beginners, reading a sequence of lines from the standard input stream can involve intimidating problems with locking and lifetimes. First, the user has to call the free function stdin()
to get a handle on the stream; then, the user would have to call the lock()
method to gain access to the lines()
method. At this point, lifetime issues rapidly arise: the following code (playground)
use std::io::{self, prelude::*};
fn main() {
let mut lines = io::stdin().lock().lines();
loop {
print!("prompt: ");
io::stdout().flush();
if let Some(_line) = lines.next() {
// do stuff
} else {
break;
}
}
}
produces this error:
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:3:21
|
3 | let mut lines = io::stdin().lock().lines();
| ^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
...
7 | if let Some(_line) = lines.next() {
| ----- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
The need to create a let
binding to the handle seems confusing and frustrating, especially if the program does not need to use the handle again. The explanation is that the lock (and the iterator produced by lines()
) behaves as if it borrows the original handle from stdin()
, and the temporary value created for the call to the lock()
method is dropped at the end of the statement, invalidating the borrow. That explanation might be beyond the current level of understanding of a beginner who simply wants to write an interactive terminal program.
Although #86845 makes it easier to obtain locked stdio handles, it would be even better if beginners didn't have to deal with the concept of locking at all at early stages of their learning.
There is precedent in the Stdin::read_line
forwarder method that implicitly locks Stdin
and calls the BufRead::read_line
method. However, read_line()
is somewhat difficult to use, because it requires that the user first allocate a String
, and it doesn't remove newlines. In contrast, lines()
provides an iterator over input lines that removes line endings, including both carriage return (CR) and line feed (LF) characters.
This proposal also includes a The remaining exclusive methods of split()
forwarder method, because it is similar in nature and usability to the lines()
method.BufRead
are less useful to beginners, and require more experience to use.
Public API
// std::io
impl Stdin {
pub fn lines(self) { /* ... */ }
}
Steps / History
- Implementation: add
Stdin::lines
,Stdin::split
forwarder methods #86847 - Delete
split
forwarder deleteStdin::split
forwarder #93134 - Final comment period (FCP)
- Stabilization PR: Stabilize Stdin::lines. #95185
Unresolved Questions
- During stabilization, we might want to update existing documentation to recommend these forwarder methods, and include examples of their use.
@rustbot label +A-io +D-newcomer-roadblock