|
| 1 | +- Feature Name: `inputln` |
| 2 | +- Start Date: 2021-11-16 |
| 3 | +- RFC PR: [rust-lang/rfcs#3196](https://github.com/rust-lang/rfcs/pull/3196) |
| 4 | + |
| 5 | +# Summary |
| 6 | +[summary]: #summary |
| 7 | + |
| 8 | +Add an `inputln` function to `std` to read a line from standard input and return |
| 9 | +a `std::io::Result<String>`. |
| 10 | + |
| 11 | +# Motivation |
| 12 | +[motivation]: #motivation |
| 13 | + |
| 14 | +Building a small interactive program that reads input from standard input and |
| 15 | +writes output to standard output is well-established as a simple and fun way of |
| 16 | +learning and teaching a new programming language. Case in point the chapter 2 |
| 17 | +of the official Rust book is [Programming a Guessing Game], which suggests the |
| 18 | +following code: |
| 19 | + |
| 20 | +[Programming a Guessing Game]: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html |
| 21 | + |
| 22 | +```rs |
| 23 | +let mut guess = String::new(); |
| 24 | + |
| 25 | +io::stdin() |
| 26 | + .read_line(&mut guess) |
| 27 | + .expect("Failed to read line"); |
| 28 | +``` |
| 29 | + |
| 30 | +While the above code is perfectly clear to everybody who already knows Rust, it |
| 31 | +can be quite overwhelming for a beginner. What is `mut`? What is `&mut`? The |
| 32 | +2nd chapter gives only basic explanations and assures that mutability and |
| 33 | +borrowing will be explained in detail in later chapters. Don't worry about that |
| 34 | +for now, everything will make sense later. But the beginner might worry about |
| 35 | +something else: Why is something so simple so complicated with Rust? For example |
| 36 | +in Python you can just do `guess = input()`. Is Rust always this cumbersome? |
| 37 | +Maybe they should rather stick with their current favorite programming language |
| 38 | +instead. |
| 39 | + |
| 40 | +This RFC therefore proposes the introduction of a `std::inputln` function so |
| 41 | +that the above example could be simplified to just: |
| 42 | + |
| 43 | +```rs |
| 44 | +let guess = inputln().expect("Failed to read line"); |
| 45 | +``` |
| 46 | + |
| 47 | +This would allow for a more graceful introduction to Rust. Letting beginners |
| 48 | +experience the exciting thrill of running their own first interactive Rust |
| 49 | +program, without being confronted with mutability and borrowing straight away. |
| 50 | +While mutability and borrowing are very powerful concepts, Rust does not force |
| 51 | +you to use them when you don't need them. The examples we use to teach Rust to |
| 52 | +complete beginners should reflect that. |
| 53 | + |
| 54 | +# Guide-level explanation |
| 55 | +[guide-level-explanation]: #guide-level-explanation |
| 56 | + |
| 57 | +`std::inputln()` is a convenience wrapper around `std::io::Stdin::read_line`, |
| 58 | +introduced so that Rust beginners can create interactive programs without having |
| 59 | +to worry about mutability or borrowing. The function allocates a new String |
| 60 | +buffer for you, and reads a line from standard input. The result is returned as |
| 61 | +a `std::io::Result<String>`. |
| 62 | + |
| 63 | +If you are repeatedly reading lines from standard input and don't need to |
| 64 | +allocate a new String for each of them you should be using |
| 65 | +`std::io::Stdin::read_line` directly instead, so that you can reuse an existing |
| 66 | +buffer. |
| 67 | + |
| 68 | +# Reference-level explanation |
| 69 | +[reference-level-explanation]: #reference-level-explanation |
| 70 | + |
| 71 | +```rs |
| 72 | +pub fn inputln() -> std::io::Result<String> { |
| 73 | + let mut input = String::new(); |
| 74 | + std::io::stdin().read_line(&mut input)?; |
| 75 | + Ok(input) |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +# Drawbacks |
| 80 | +[drawbacks]: #drawbacks |
| 81 | + |
| 82 | +* Can lead to unnecessary buffer allocations in Rust programs when developers |
| 83 | + don't realize that they could reuse a buffer instead. This could potentially |
| 84 | + be remedied by a new Clippy lint. |
| 85 | + |
| 86 | +* There is no precedent for a function residing directly in the `std` module |
| 87 | + (currently it only contains macros). So Rust programmers might out of habit |
| 88 | + try to call `inputln!()`. This should however not pose a big hurdle because |
| 89 | + `rustc` already provides a helpful error message: |
| 90 | + |
| 91 | + ``` |
| 92 | + error: cannot find macro `inputln` in this scope |
| 93 | + --> src/main.rs:13:5 |
| 94 | + | |
| 95 | + 13 | inputln!(); |
| 96 | + | ^^^^^^^ |
| 97 | + | |
| 98 | + = note: `inputln` is in scope, but it is a function, not a macro |
| 99 | + ``` |
| 100 | + |
| 101 | +# Rationale and alternatives |
| 102 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 103 | + |
| 104 | +> Why is this design the best in the space of possible designs? |
| 105 | +
|
| 106 | +It is the simplest solution to the explained problem. |
| 107 | + |
| 108 | +> What other designs have been considered and what is the rationale for not |
| 109 | +> choosing them? |
| 110 | +
|
| 111 | +The function could also be implemented as a macro but there is really no need for that. |
| 112 | + |
| 113 | +> What is the impact of not doing this? |
| 114 | +
|
| 115 | +A higher chance of Rust beginners getting overwhelmed by mutability and borrowing. |
| 116 | + |
| 117 | +# Prior art |
| 118 | +[prior-art]: #prior-art |
| 119 | + |
| 120 | +Python has [input()], Ruby has [gets], C# has `Console.ReadLine()` |
| 121 | +... all of these return a string read from standard input. |
| 122 | + |
| 123 | +[input()]: https://docs.python.org/3/library/functions.html#input |
| 124 | +[gets]: https://ruby-doc.org/docs/ruby-doc-bundle/Tutorial/part_02/user_input.html |
| 125 | + |
| 126 | +Other standard libraries additionally: |
| 127 | + |
| 128 | +* accept a prompt to display to the user before reading from standard input |
| 129 | + (e.g. Python and Node.js) |
| 130 | + |
| 131 | +* provide some functions to parse multiple values of specific data types |
| 132 | + into ovariables (e.g. C's `scanf`, C++, Java's `Scanner`) |
| 133 | + |
| 134 | +Python's `input()` function accepts a `prompt` argument because Python's output |
| 135 | +is line buffered by default, meaning a `print()` without a newline would only be |
| 136 | +output after a manual flush. Node.js accepts a prompt because its |
| 137 | +[readline](https://nodejs.org/api/readline.html) interface is very high level. |
| 138 | +Both reasonings don't apply to Rust. With Rust a simple `print!()` call before |
| 139 | +invoking `inputln()` suffices to display an input prompt and more high-level |
| 140 | +interfaces are better provided by crates. |
| 141 | + |
| 142 | +While scanning utilities could also be added to the Rust standard library, how |
| 143 | +these should be designed is less clear, as well as whether or not they should be |
| 144 | +in the standard library in the first place. There exist many well established |
| 145 | +input parsing libraries for Rust that are only a `cargo install` away. The same |
| 146 | +argument does not apply to `inputln()` ... beginners should be able to get |
| 147 | +started with an interactive Rust program without having to worry about |
| 148 | +mutability, borrowing or having to install a third-party library. |
| 149 | + |
| 150 | +# Unresolved questions |
| 151 | +[unresolved-questions]: #unresolved-questions |
| 152 | + |
| 153 | +> What parts of the design do you expect to resolve through the RFC process |
| 154 | +> before this gets merged? |
| 155 | +
|
| 156 | +The name of the function is up to debate. `read_line()` would also be a |
| 157 | +reasonable choice, that does however potentially beg the question: Read from |
| 158 | +where? `inputln()` hints that the line comes from standard input. |
| 159 | + |
| 160 | +The location of the function is also up to debate. It could also reside in |
| 161 | +`std::io`, which would however come with the drawback that beginners need to |
| 162 | +either import it or prefix `std::io`, both of which seem like unnecessary |
| 163 | +hurdles. |
| 164 | + |
| 165 | +> What related issues do you consider out of scope for this RFC that could be |
| 166 | +> addressed in the future independently of the solution that comes out of this RFC? |
| 167 | +
|
| 168 | +I consider the question whether or not scanning utilities should be added to the |
| 169 | +standard library to be out of the scope of this RFC. |
| 170 | + |
| 171 | +# Future possibilities |
| 172 | +[future-possibilities]: #future-possibilities |
| 173 | + |
| 174 | +Once this RFC is implemented the Chapter 2 of the Rust book could be simplified |
| 175 | +to introduce mutability and borrowing in a more gentle manner. Clippy could gain |
| 176 | +a lint to tell users to avoid unnecessary allocations due to repeated |
| 177 | +`inputln()` calls and suggest `std::io::Stdin::read_line` instead. |
| 178 | + |
| 179 | +With this addition Rust might lend itself more towards being the first |
| 180 | +programming language for students. |
0 commit comments