From 13093525409706387358dec6835ecd3416f16daf Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Tue, 16 Nov 2021 10:52:12 +0100 Subject: [PATCH 01/12] Add RFC for std::inputln() --- text/3196-inputln.md | 180 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 text/3196-inputln.md diff --git a/text/3196-inputln.md b/text/3196-inputln.md new file mode 100644 index 00000000000..4289138a497 --- /dev/null +++ b/text/3196-inputln.md @@ -0,0 +1,180 @@ +- Feature Name: `inputln` +- Start Date: 2021-11-16 +- RFC PR: [rust-lang/rfcs#3196](https://github.com/rust-lang/rfcs/pull/3196) + +# Summary +[summary]: #summary + +Add an `inputln` function to `std` to read a line from standard input and return +a `std::io::Result`. + +# Motivation +[motivation]: #motivation + +Building a small interactive program that reads input from standard input and +writes output to standard output is well-established as a simple and fun way of +learning and teaching a new programming language. Case in point the chapter 2 +of the official Rust book is [Programming a Guessing Game], which suggests the +following code: + +[Programming a Guessing Game]: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html + +```rs +let mut guess = String::new(); + +io::stdin() + .read_line(&mut guess) + .expect("Failed to read line"); +``` + +While the above code is perfectly clear to everybody who already knows Rust, it +can be quite overwhelming for a beginner. What is `mut`? What is `&mut`? The +2nd chapter gives only basic explanations and assures that mutability and +borrowing will be explained in detail in later chapters. Don't worry about that +for now, everything will make sense later. But the beginner might worry about +something else: Why is something so simple so complicated with Rust? For example +in Python you can just do `guess = input()`. Is Rust always this cumbersome? +Maybe they should rather stick with their current favorite programming language +instead. + +This RFC therefore proposes the introduction of a `std::inputln` function so +that the above example could be simplified to just: + +```rs +let guess = inputln().expect("Failed to read line"); +``` + +This would allow for a more graceful introduction to Rust. Letting beginners +experience the exciting thrill of running their own first interactive Rust +program, without being confronted with mutability and borrowing straight away. +While mutability and borrowing are very powerful concepts, Rust does not force +you to use them when you don't need them. The examples we use to teach Rust to +complete beginners should reflect that. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +`std::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`, +introduced so that Rust beginners can create interactive programs without having +to worry about mutability or borrowing. The function allocates a new String +buffer for you, and reads a line from standard input. The result is returned as +a `std::io::Result`. + +If you are repeatedly reading lines from standard input and don't need to +allocate a new String for each of them you should be using +`std::io::Stdin::read_line` directly instead, so that you can reuse an existing +buffer. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +```rs +pub fn inputln() -> std::io::Result { + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + Ok(input) +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +* Can lead to unnecessary buffer allocations in Rust programs when developers + don't realize that they could reuse a buffer instead. This could potentially + be remedied by a new Clippy lint. + +* There is no precedent for a function residing directly in the `std` module + (currently it only contains macros). So Rust programmers might out of habit + try to call `inputln!()`. This should however not pose a big hurdle because + `rustc` already provides a helpful error message: + + ``` + error: cannot find macro `inputln` in this scope + --> src/main.rs:13:5 + | + 13 | inputln!(); + | ^^^^^^^ + | + = note: `inputln` is in scope, but it is a function, not a macro + ``` + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +> Why is this design the best in the space of possible designs? + +It is the simplest solution to the explained problem. + +> What other designs have been considered and what is the rationale for not +> choosing them? + +The function could also be implemented as a macro but there is really no need for that. + +> What is the impact of not doing this? + +A higher chance of Rust beginners getting overwhelmed by mutability and borrowing. + +# Prior art +[prior-art]: #prior-art + +Python has [input()], Ruby has [gets], C# has `Console.ReadLine()` +... all of these return a string read from standard input. + +[input()]: https://docs.python.org/3/library/functions.html#input +[gets]: https://ruby-doc.org/docs/ruby-doc-bundle/Tutorial/part_02/user_input.html + +Other standard libraries additionally: + +* accept a prompt to display to the user before reading from standard input + (e.g. Python and Node.js) + +* provide some functions to parse multiple values of specific data types + into ovariables (e.g. C's `scanf`, C++, Java's `Scanner`) + +Python's `input()` function accepts a `prompt` argument because Python's output +is line buffered by default, meaning a `print()` without a newline would only be +output after a manual flush. Node.js accepts a prompt because its +[readline](https://nodejs.org/api/readline.html) interface is very high level. +Both reasonings don't apply to Rust. With Rust a simple `print!()` call before +invoking `inputln()` suffices to display an input prompt and more high-level +interfaces are better provided by crates. + +While scanning utilities could also be added to the Rust standard library, how +these should be designed is less clear, as well as whether or not they should be +in the standard library in the first place. There exist many well established +input parsing libraries for Rust that are only a `cargo install` away. The same +argument does not apply to `inputln()` ... beginners should be able to get +started with an interactive Rust program without having to worry about +mutability, borrowing or having to install a third-party library. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +> What parts of the design do you expect to resolve through the RFC process +> before this gets merged? + +The name of the function is up to debate. `read_line()` would also be a +reasonable choice, that does however potentially beg the question: Read from +where? `inputln()` hints that the line comes from standard input. + +The location of the function is also up to debate. It could also reside in +`std::io`, which would however come with the drawback that beginners need to +either import it or prefix `std::io`, both of which seem like unnecessary +hurdles. + +> What related issues do you consider out of scope for this RFC that could be +> addressed in the future independently of the solution that comes out of this RFC? + +I consider the question whether or not scanning utilities should be added to the +standard library to be out of the scope of this RFC. + +# Future possibilities +[future-possibilities]: #future-possibilities + +Once this RFC is implemented the Chapter 2 of the Rust book could be simplified +to introduce mutability and borrowing in a more gentle manner. Clippy could gain +a lint to tell users to avoid unnecessary allocations due to repeated +`inputln()` calls and suggest `std::io::Stdin::read_line` instead. + +With this addition Rust might lend itself more towards being the first +programming language for students. From b2de87d39b1dbad87c44fc5c30046d2d2f3d61e4 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Tue, 16 Nov 2021 21:54:00 +0100 Subject: [PATCH 02/12] Update RFC 3196 to place inputln in std::io Since it's a function it can be added to std::prelude. --- text/3196-inputln.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 4289138a497..962115455b3 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -5,8 +5,8 @@ # Summary [summary]: #summary -Add an `inputln` function to `std` to read a line from standard input and return -a `std::io::Result`. +Add an `inputln` function to `std::io` to read a line from standard input and +return a `std::io::Result`. # Motivation [motivation]: #motivation @@ -37,11 +37,11 @@ in Python you can just do `guess = input()`. Is Rust always this cumbersome? Maybe they should rather stick with their current favorite programming language instead. -This RFC therefore proposes the introduction of a `std::inputln` function so -that the above example could be simplified to just: +This RFC therefore proposes the introduction of a `std::io::inputln` function +so that the above example could be simplified to just: ```rs -let guess = inputln().expect("Failed to read line"); +let guess = io::inputln().expect("Failed to read line"); ``` This would allow for a more graceful introduction to Rust. Letting beginners @@ -54,7 +54,7 @@ complete beginners should reflect that. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -`std::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`, +`std::io::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`, introduced so that Rust beginners can create interactive programs without having to worry about mutability or borrowing. The function allocates a new String buffer for you, and reads a line from standard input. The result is returned as @@ -83,10 +83,10 @@ pub fn inputln() -> std::io::Result { don't realize that they could reuse a buffer instead. This could potentially be remedied by a new Clippy lint. -* There is no precedent for a function residing directly in the `std` module - (currently it only contains macros). So Rust programmers might out of habit - try to call `inputln!()`. This should however not pose a big hurdle because - `rustc` already provides a helpful error message: +* `println!` and `writeln!` are both macros, so Rust programmers might out of + habit try to call `inputln!()`. This should however not pose a big hurdle if + `std::io::inputln` is added to `std::prelude` because in that case `rustc` + already provides a helpful error message: ``` error: cannot find macro `inputln` in this scope @@ -157,10 +157,8 @@ The name of the function is up to debate. `read_line()` would also be a reasonable choice, that does however potentially beg the question: Read from where? `inputln()` hints that the line comes from standard input. -The location of the function is also up to debate. It could also reside in -`std::io`, which would however come with the drawback that beginners need to -either import it or prefix `std::io`, both of which seem like unnecessary -hurdles. +Should the function additionally be added to `std::prelude`, so that beginners +can use it without needing to import `std::io`? > What related issues do you consider out of scope for this RFC that could be > addressed in the future independently of the solution that comes out of this RFC? From 7f9ae15c5b80147be3ed7946fcdffd8ec30d9ecd Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Tue, 16 Nov 2021 23:50:33 +0100 Subject: [PATCH 03/12] Update RFC 3196 to trim newlines --- text/3196-inputln.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 962115455b3..cf5aae9ab17 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -5,8 +5,8 @@ # Summary [summary]: #summary -Add an `inputln` function to `std::io` to read a line from standard input and -return a `std::io::Result`. +Add an `inputln` convenience function to `std::io` to read a line from standard +input and return a `std::io::Result`. # Motivation [motivation]: #motivation @@ -54,11 +54,10 @@ complete beginners should reflect that. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -`std::io::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`, -introduced so that Rust beginners can create interactive programs without having -to worry about mutability or borrowing. The function allocates a new String -buffer for you, and reads a line from standard input. The result is returned as -a `std::io::Result`. +`std::io::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`. +The function allocates a new String buffer for you, reads a line from standard +input and strips the newline (`\n` or `\r\n`). The result is returned as a +`std::io::Result`. If you are repeatedly reading lines from standard input and don't need to allocate a new String for each of them you should be using @@ -72,6 +71,13 @@ buffer. pub fn inputln() -> std::io::Result { let mut input = String::new(); std::io::stdin().read_line(&mut input)?; + + if input.ends_with('\n') { + input.pop(); + if input.ends_with('\r') { + input.pop(); + } + } Ok(input) } ``` @@ -105,6 +111,8 @@ pub fn inputln() -> std::io::Result { It is the simplest solution to the explained problem. +The newline stripping behavior is the same as of `std::io::BufRead::lines`. + > What other designs have been considered and what is the rationale for not > choosing them? From 57bbb431c531a7e1743dc4382843fb346420e961 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 18 Nov 2021 06:32:48 +0100 Subject: [PATCH 04/12] inputln: Add rationale for newline trimming --- text/3196-inputln.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index cf5aae9ab17..7a0913a3107 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -82,6 +82,8 @@ pub fn inputln() -> std::io::Result { } ``` +The newline trimming behavior is the same as of `std::io::BufRead::lines`. + # Drawbacks [drawbacks]: #drawbacks @@ -107,11 +109,21 @@ pub fn inputln() -> std::io::Result { # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -> Why is this design the best in the space of possible designs? +> Why should the function trim newlines? + +We assume that the returned string will often be processed with `String::parse`, +for example it is likely that developers will attempt the following: -It is the simplest solution to the explained problem. +```rs +let age: i32 = io::inputln()?.parse()?; +``` -The newline stripping behavior is the same as of `std::io::BufRead::lines`. +If `inputln()` didn't trim newlines the above would however always fail since +the `FromStr` implementations in the standard library don't expect a trailing +newline. Newline trimming is therefore included for better ergonomics, so that +programmers don't have to remember to add a `trim()` call whenever they want to +parse the returned string. In cases where newlines have to be preserved the +underlying `std::io::Stdin::read_line` can be used directly instead. > What other designs have been considered and what is the rationale for not > choosing them? From 142b0a98e28fa13eee04524adacb2e6de9b3c6e1 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 18 Nov 2021 07:14:52 +0100 Subject: [PATCH 05/12] inputln: Add EOF handling --- text/3196-inputln.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 7a0913a3107..5334d9d0d13 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -56,8 +56,9 @@ complete beginners should reflect that. `std::io::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`. The function allocates a new String buffer for you, reads a line from standard -input and strips the newline (`\n` or `\r\n`). The result is returned as a -`std::io::Result`. +input and trims the newline (`\n` or `\r\n`). The result is returned as a +`std::io::Result`. When the input stream has reached EOF a +`std::io::Error` of kind `ErrorKind::UnexpectedEof` is returned. If you are repeatedly reading lines from standard input and don't need to allocate a new String for each of them you should be using @@ -68,9 +69,17 @@ buffer. [reference-level-explanation]: #reference-level-explanation ```rs +use std::io::{Error, ErrorKind}; + pub fn inputln() -> std::io::Result { let mut input = String::new(); - std::io::stdin().read_line(&mut input)?; + + if std::io::stdin().read_line(&mut input)? == 0 { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "EOF while reading a line", + )); + } if input.ends_with('\n') { input.pop(); @@ -125,6 +134,30 @@ programmers don't have to remember to add a `trim()` call whenever they want to parse the returned string. In cases where newlines have to be preserved the underlying `std::io::Stdin::read_line` can be used directly instead. +> Why should the function handle EOF? + +If the function performs newline trimming, it also has to return an error when +the input stream reaches EOF because otherwise users had no chance of detecting +EOF. Handling EOF allows for example a program that attempts to parse each line +as a number and prints their sum on EOF to be implemented as follows: + +```rs +fn main() -> std::io::Result<()> { + let mut sum = 0; + loop { + match inputln() { + Ok(line) => sum += line.parse::().expect("not a number"), + Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, + Err(other_error) => { + return Err(other_error); + } + } + } + println!("{}", sum); + Ok(()) +} +``` + > What other designs have been considered and what is the rationale for not > choosing them? From 87b78e2c1d3fbbeda15f84ebddfe7b82e11a53ea Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 18 Nov 2021 08:29:14 +0100 Subject: [PATCH 06/12] inputln: Mention that print!() does not flush --- text/3196-inputln.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 5334d9d0d13..83af4dd9279 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -115,6 +115,26 @@ The newline trimming behavior is the same as of `std::io::BufRead::lines`. = note: `inputln` is in scope, but it is a function, not a macro ``` +* Might lead to confusion when users attempt the following: + + ```rs + print!("enter your name: "); + let name = io::inputln()?; + ``` + + Because the `print!` macro does not flush stdout the prompt will only ever be + displayed after the user has input their name. The correct way to implement + the above would be: + + ```rs + print!("enter your name: "); + io::stdout().flush()?; + let name = io::inputln()?; + ``` + + This can be somewhat remedied by adding the above example to the `inputln()` + documentation. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -184,13 +204,11 @@ Other standard libraries additionally: * provide some functions to parse multiple values of specific data types into ovariables (e.g. C's `scanf`, C++, Java's `Scanner`) -Python's `input()` function accepts a `prompt` argument because Python's output -is line buffered by default, meaning a `print()` without a newline would only be -output after a manual flush. Node.js accepts a prompt because its -[readline](https://nodejs.org/api/readline.html) interface is very high level. -Both reasonings don't apply to Rust. With Rust a simple `print!()` call before -invoking `inputln()` suffices to display an input prompt and more high-level -interfaces are better provided by crates. +Python's `input()` function can additionally make use of the GNU readline +library and Node.js' [readline](https://nodejs.org/api/readline.html) interface +provides a history and TTY keybindings as well. The function suggested in this +RFC does not include such high-level features, these are better left to crates, +such as [`rustyline`](https://crates.io/crates/rustyline). While scanning utilities could also be added to the Rust standard library, how these should be designed is less clear, as well as whether or not they should be From 8abd1a75af25a403b6ca01c655ff8d0697227b9a Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 20 Nov 2021 05:29:38 +0100 Subject: [PATCH 07/12] inputln: Suggest Clippy lint for missing flush --- text/3196-inputln.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 83af4dd9279..3ae8e0343e5 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -132,8 +132,8 @@ The newline trimming behavior is the same as of `std::io::BufRead::lines`. let name = io::inputln()?; ``` - This can be somewhat remedied by adding the above example to the `inputln()` - documentation. + This source of confusion could be mitigated by explaining the issue in the + documentation of `inputln()` and introducing a respective Clippy lint. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -240,10 +240,17 @@ standard library to be out of the scope of this RFC. # Future possibilities [future-possibilities]: #future-possibilities -Once this RFC is implemented the Chapter 2 of the Rust book could be simplified -to introduce mutability and borrowing in a more gentle manner. Clippy could gain -a lint to tell users to avoid unnecessary allocations due to repeated -`inputln()` calls and suggest `std::io::Stdin::read_line` instead. +Once this RFC is implemented: + +* The Chapter 2 of the Rust book could be simplified + to introduce mutability and borrowing in a more gentle manner. + +* Clippy should gain a lint that detects `print!(...); let x = io::inputln();` + and suggests you to insert a `io::stdout().flush();` between the statements. + +* Clippy might also introduce a lint to tell users to avoid unnecessary + allocations due to repeated `inputln()` calls and suggest + `std::io::Stdin::read_line` instead. With this addition Rust might lend itself more towards being the first programming language for students. From 8bea7dc9d1677649f12f58535f5460f15b8f4209 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 20 Nov 2021 06:13:28 +0100 Subject: [PATCH 08/12] inputln: Elaborate on function vs. macro --- text/3196-inputln.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 3ae8e0343e5..36aeae219c7 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -178,10 +178,30 @@ fn main() -> std::io::Result<()> { } ``` -> What other designs have been considered and what is the rationale for not -> choosing them? +> Why should the function be implemented as a function instead of a macro? -The function could also be implemented as a macro but there is really no need for that. +If the function were implemented as a macro it could take an optional `prompt` +argument and take care of flushing stdout between printing the prompt and +reading from stdin. + +Since the function is however meant to facilitate teaching Rust to complete +beginners it should be as beginner-friendly as possible, which also entails +implementing it as an actual function because then it has a clear signature: + +```rs +pub fn inputln() -> std::io::Result +``` + +As opposed to a macro for which `rustdoc` would show something like: + +```rs +macro_rules! prompt { + () => { ... }; + ($($args : tt) +) => { ... }; +} +``` + +which is not at all helpful for a beginner trying to understand what's going on. > What is the impact of not doing this? From dd341ff8aa13ebd942d32c2032c90540d800b8d0 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 20 Nov 2021 06:33:28 +0100 Subject: [PATCH 09/12] inputln: Elaborate on function naming --- text/3196-inputln.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 36aeae219c7..35ba80cb1c4 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -244,9 +244,16 @@ mutability, borrowing or having to install a third-party library. > What parts of the design do you expect to resolve through the RFC process > before this gets merged? -The name of the function is up to debate. `read_line()` would also be a -reasonable choice, that does however potentially beg the question: Read from -where? `inputln()` hints that the line comes from standard input. +The name of the function is up to debate. `read_line` would also be an obvious +choice because the function wraps `std::io::Stdin::read_line`. Since the +function however additionally performs newline trimming and yields an error for +EOF (as opposed to returning an `Ok` variant), naming it the same might mislead +users into thinking that the function does not have these subtle differences. +In particular because there is precedent for convenience functions that share +the name of their underlying function to also behave the same +(`std::io::read_to_string` and `std::fs::read_to_string` both wrap +`Read::read_to_string` without processing the string or introducing additional +error sources). Should the function additionally be added to `std::prelude`, so that beginners can use it without needing to import `std::io`? From 389cc75435d88ef263c256584758e368cd46ab13 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 20 Nov 2021 09:17:47 +0100 Subject: [PATCH 10/12] inputln: Reword motivation --- text/3196-inputln.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 35ba80cb1c4..bb128241593 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -28,17 +28,11 @@ io::stdin() ``` While the above code is perfectly clear to everybody who already knows Rust, it -can be quite overwhelming for a beginner. What is `mut`? What is `&mut`? The -2nd chapter gives only basic explanations and assures that mutability and -borrowing will be explained in detail in later chapters. Don't worry about that -for now, everything will make sense later. But the beginner might worry about -something else: Why is something so simple so complicated with Rust? For example -in Python you can just do `guess = input()`. Is Rust always this cumbersome? -Maybe they should rather stick with their current favorite programming language -instead. - -This RFC therefore proposes the introduction of a `std::io::inputln` function -so that the above example could be simplified to just: +can be quite overwhelming for a beginner because it confronts them with three +new concepts at once: mutability, borrowing and the `Result` type. Didactically +it would be better if these concepts could be introduced one at a time. This +RFC therefore proposes the introduction of a `std::io::inputln` function so +that the above example could be simplified to just: ```rs let guess = io::inputln().expect("Failed to read line"); From bd4908862abd207199ffe0be8ed54085351b9f7e Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Wed, 24 Nov 2021 23:35:58 +0100 Subject: [PATCH 11/12] inputln: Flush standard output --- text/3196-inputln.md | 64 ++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index bb128241593..887c8178e86 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -63,9 +63,11 @@ buffer. [reference-level-explanation]: #reference-level-explanation ```rs -use std::io::{Error, ErrorKind}; +use std::io::{Error, ErrorKind, Write}; pub fn inputln() -> std::io::Result { + std::io::stdout().flush()?; // because print! doesn't flush + let mut input = String::new(); if std::io::stdin().read_line(&mut input)? == 0 { @@ -109,26 +111,6 @@ The newline trimming behavior is the same as of `std::io::BufRead::lines`. = note: `inputln` is in scope, but it is a function, not a macro ``` -* Might lead to confusion when users attempt the following: - - ```rs - print!("enter your name: "); - let name = io::inputln()?; - ``` - - Because the `print!` macro does not flush stdout the prompt will only ever be - displayed after the user has input their name. The correct way to implement - the above would be: - - ```rs - print!("enter your name: "); - io::stdout().flush()?; - let name = io::inputln()?; - ``` - - This source of confusion could be mitigated by explaining the issue in the - documentation of `inputln()` and introducing a respective Clippy lint. - # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -172,15 +154,42 @@ fn main() -> std::io::Result<()> { } ``` +> Why should the function flush standard output before reading? + +Users are bound to display prompts with the `print!` macro, for example they +might do: + +```rs +print!("enter your name: "); +let name = io::inputln()?; +``` + +The `print!` macro however does not flush standard output, meaning if `inputln` +wouldn't flush standard output, the above code would read from standard input +without printing anything. `inputln` should therefore flush stdout to spare +users from walking into this pitfall. + +The overhead this poses is negligible. Flushing standard output is effectively +a no-op if there's no buffered data. While flushing standard output can fail if +the file descriptor has been closed, the assumption in Rust programs generally +is that standard output is always both open and writable. For example the +`print!` and `println!` macros panic when their writing to standard output +fails, and [since 1.48.0 Rust reopens the standard file +descriptors](https://github.com/rust-lang/rust/pull/75295) with `/dev/null` +when they are closed on startup. While it has been suggested to [add a method +that closes standard output to the standard +library](https://github.com/rust-lang/rust/issues/40032), the proposal also +elaborated that standard output would immediately be reopend with `/dev/null` +to uphold that very assumption. + > Why should the function be implemented as a function instead of a macro? If the function were implemented as a macro it could take an optional `prompt` -argument and take care of flushing stdout between printing the prompt and -reading from stdin. +argument and only flush standard output when it is actually needed. -Since the function is however meant to facilitate teaching Rust to complete -beginners it should be as beginner-friendly as possible, which also entails -implementing it as an actual function because then it has a clear signature: +This function might however very well be the first time a Rust beginner +encounters the `Result` type, so it should really be impemented as an actual +function, so that it has a clear signature: ```rs pub fn inputln() -> std::io::Result @@ -266,9 +275,6 @@ Once this RFC is implemented: * The Chapter 2 of the Rust book could be simplified to introduce mutability and borrowing in a more gentle manner. -* Clippy should gain a lint that detects `print!(...); let x = io::inputln();` - and suggests you to insert a `io::stdout().flush();` between the statements. - * Clippy might also introduce a lint to tell users to avoid unnecessary allocations due to repeated `inputln()` calls and suggest `std::io::Stdin::read_line` instead. From 126e2bd1f6853ec2701cc041104917640f94bb2f Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Wed, 24 Nov 2021 23:42:54 +0100 Subject: [PATCH 12/12] inputln: Format questions as subheadings --- text/3196-inputln.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/3196-inputln.md b/text/3196-inputln.md index 887c8178e86..74a234c968d 100644 --- a/text/3196-inputln.md +++ b/text/3196-inputln.md @@ -114,7 +114,7 @@ The newline trimming behavior is the same as of `std::io::BufRead::lines`. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -> Why should the function trim newlines? +## Why should the function trim newlines? We assume that the returned string will often be processed with `String::parse`, for example it is likely that developers will attempt the following: @@ -130,7 +130,7 @@ programmers don't have to remember to add a `trim()` call whenever they want to parse the returned string. In cases where newlines have to be preserved the underlying `std::io::Stdin::read_line` can be used directly instead. -> Why should the function handle EOF? +## Why should the function handle EOF? If the function performs newline trimming, it also has to return an error when the input stream reaches EOF because otherwise users had no chance of detecting @@ -154,7 +154,7 @@ fn main() -> std::io::Result<()> { } ``` -> Why should the function flush standard output before reading? +## Why should the function flush standard output before reading? Users are bound to display prompts with the `print!` macro, for example they might do: @@ -182,7 +182,7 @@ library](https://github.com/rust-lang/rust/issues/40032), the proposal also elaborated that standard output would immediately be reopend with `/dev/null` to uphold that very assumption. -> Why should the function be implemented as a function instead of a macro? +## Why should the function be implemented as a function instead of a macro? If the function were implemented as a macro it could take an optional `prompt` argument and only flush standard output when it is actually needed. @@ -206,7 +206,7 @@ macro_rules! prompt { which is not at all helpful for a beginner trying to understand what's going on. -> What is the impact of not doing this? +## What is the impact of not doing this? A higher chance of Rust beginners getting overwhelmed by mutability and borrowing.