owned this note
owned this note
Published
Linked with GitHub
Rust Input
==========
- Feature Name: (fill me in with a unique ident, `input`)
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
State: the author don't think it is so useful to add it to the standard library after writing this RFC.
# Summary
[summary]: #summary
One-liner to read single line input.
# Motivation
[motivation]: #motivation
Condenses the normal input process into one function. This is good for new rust users who don't understand the normal process and for users who don't require special functionality.
Users may even need to search [stackoverflow 37k views](https://stackoverflow.com/questions/13579266/how-to-read-user-input-in-rust) to figure out how to do this.
```rust
use std::io::{self, Write};
print!("Please enter some text: ");
stdout().flush().unwrap();
let mut s = String::new();
stdin().read_line(&mut s).expect("Did not enter a correct string");
let s = s.trim();
println!("You typed: {}", s);
```
This feature targets beginners new to rust and being useful for prototyping.
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
As there are many possible designs available for this, we would first like to lay out different designs with their pros and cons and figure out which is a good method.
To compare against different workloads, we provide examples for the most common use of input for these cases:
- `String` input (`name`)
- seen not sure where but somewhere
- `usize` input (`age`)
- seen in number guessing game in rust book
- Configuration input (`Y/n`)
- seen in most package managers
Other unsupported rare use cases (should we support these as well, feels out of scope):
- Single character input
- Selection input (multi-choice selection)
- Password input
## Single-input function
```rust
use std::io::{self, Error, ErrorKind};
fn input() -> io::Result<String> {
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
if s.ends_with('\n') {
s.pop();
if cfg!(windows) && s.ends_with('\r') {
s.pop();
}
Ok(s)
} else {
Err(Error::new(ErrorKind::UnexpectedEof, "input received end of file"))
}
}
fn main() {
// use std::io::input;
print!("name: ");
io::stdout().flush()?;
let name = input()?;
print!("name: {}", name);
print!("age: ");
io::stdout().flush()?;
let age: usize = input()?.parse().expect("unbelievable");
println!("age: {}", age);
print!("confirm? [Y/n]");
io::stdout().flush()?;
let confirm = input()?;
let confirm = matches!(confirm.as_str(), "" | "Y" | "y");
println!("confirm: {}", confirm);
Ok(())
}
```
- pros
- easy to use
- cons
- inefficient
- this can be (partially) solved by documentation
* origin
- https://github.com/rust-lang/rust/pull/75435
- https://github.com/rust-lang/rust/pull/74178
* variants (maybe separate this into their own versions?)
- flush `stdout` when called call
+ user does not need to flush stdout themselves
* feels magical if not written in the docs, should be linted
- extra unnecessary syscall if not needed
* either way this is not recommended for, should be documented
- parse string using `TryInto`
+ one line lesser when parsing is used
* this will be useful when users needs to parse the `String`
* may not always be good to merge input capture and parsing
- extra unnecessary type when no conversion
* type still needed during `String -> String`
- custom aggregation error type needed
* causing type polution (`io::Error + ManualError`)
* may not work well with `?` propagation without custom type
- separate prompt function TODO
- `input` only takes input, `prompt` additionally write to stdout
* From https://github.com/rust-lang/rust/pull/74178
* bikeshed
- naming may conflict with `stdin().read_line()`
- newline behavior differs
- naming may conflict with `println!()`
- line vs ln
- naming may conflict with `fs::read_to_string()`
- newline behavior differs?
## Declarative Macros
```rust
macro_rules! inputln {
() => {
input()
};
($prompt:expr) => ({
print!($prompt);
io::stdout().flush().and_then(|_| input())
});
}
fn main() {
// use std::io::inputln; // or could be in instrinsics
let name = inputln!("name: ")?;
println!("name: {}", name);
let age: usize = inputln!("age: ")?.parse().expect("unbelievable");
println!("age: {}", age);
let confirm = inputln!("confirm? [Y/n] ")?;
let confirm = matches!(confirm.as_str(), "" | "Y" | "y");
println!("confirm: {}", confirm);
Ok(())
}
```
* variants
- accept format args, behaves like `print!()` and friends
+ become closer to `print!()` and friends
- `FormatArgs` (`print!()` uses) is not easily optimizable by compiler
## Input (ignore eof) and parse function
```rust
use std::io;
use std::str::FromStr;
fn read_input<T: FromStr>(message: &str) -> Result<T, T::Err> {
let mut stdout = io::stdout();
stdout
.write_all(message.as_bytes())
.expect("failed to write to stdout");
stdout.flush().expect("failed to flush stdout");
let mut s = String::new();
io::stdin().read_line(&mut s).expect("unexpected eof");
if s.ends_with('\n') {
s.pop();
if cfg!(windows) && s.ends_with('\r') {
s.pop();
}
T::from_str(&s)
} else {
panic!("unexpected eof")
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// use std::io::read_input;
let name: String = read_input("name: ")?;
println!("name: {}", name);
let age: usize = read_input("age: ")?;
println!("age: {}", age);
let confirm: String = read_input("confirm? [Y/n] ")?;
let confirm = matches!(confirm.as_str(), "" | "Y" | "y");
println!("confirm: {}", confirm);
}
```
+ reduce an extra line to handle parsing logic
- one could argue that parsing and input should be different since it is very different
+ opens up possibility to use `char` to read a single character
- one still should not use `Vec<String>` which may easily cause confusion between users
+ compiler can better optimized it with `&str` since it does not have impure `FormatArgs`
- this method could also be applied for the other cases described
- unable to handle EOF since it panics on eof
- error incompatibility between parsed error and input error (also affects `?`)
- likely requires `Box<dyn std::error::Error>` as `Error` that beginners feels magical
- requires explicit type signature even if users just a `String`
- useful for cases that require parsing but make the rest harder
- easily have errors that beginners may hardly understand (if they happened to miss type annotation)
- could be fixed by compiler suggesting how to label missing type annotation
```rust
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> src/main.rs:74:26
|
74 | println!("name: {}", name);
| ^^^^ `()` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `()`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required by `std::fmt::Display::fmt`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `(): std::str::FromStr` is not satisfied
--> src/main.rs:73:16
|
28 | fn read_input<T: FromStr>(message: &str) -> Result<T, T::Err> {
| ------- required by this bound in `read_input`
...
73 | let name = read_input("name: ")?;
| ^^^^^^^^^^ the trait `std::str::FromStr` is not implemented for `()`
error[E0277]: the trait bound `(): std::str::FromStr` is not satisfied
--> src/main.rs:73:16
|
73 | let name = read_input("name: ")?;
| ^^^^^^^^^^^^^^^^^^^^ the trait `std::str::FromStr` is not implemented for `()`
```
* origin
- https://internals.rust-lang.org/t/add-plain-simple-way-in-the-standard-library-for-reading-values-from-stdin/10846
* variants
- error handled by user rather than panic
- probably hard since io and parsing errors are different
## Procedural Macros (never though about this, is it even useful?)
- Not aware of how useful is this
## Teaching it to beginners
- teach it in rust book (chapter 3 IIRC)
- mention in the docs how to use `Read` and `BufRead` to suggest a more efficient implementation
- as a step to let developers understand what is `BufRead` and how is it useful (not all developers know io buffers)
- even the current `Read` may be inefficient without buffers
- putting how to use io buffers there (`Read`) may not be good since it includes many other stuff
- putting how to use io buffers here (`input`) may be good since it is suitable for beginners to understand related topics
- if this is being published as a crate (or maybe even under rust-lang), can it be included in the book for beginners?
- one reason why having it within the standard library is that it can be easily included in the book
- but still it will bloat the standard library such that it is only a material for learning and prototyping
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
This is the technical portion of the RFC. Explain the design in sufficient detail that:
- Its interaction with other features is clear.
- It is reasonably clear how the feature would be implemented.
- Corner cases are dissected by example.
- When working on the docs, clearly mention better alternatives.
- Change the related section in the book.
https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work.
# Drawbacks
[drawbacks]: #drawbacks
Why should we *not* do this?
- Reduce control on memory allocation
- Introduce inefficiency by design
# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
- Why is this design the best in the space of possible designs?
- Nope, that is why this proposal is being created to review the design space
- What other designs have been considered and what is the rationale for not choosing them?
- Disallow using this for production build
- Why not hack existing proc macros for `println!("input", input)`? Looks magical to me. T_T
- Make this part of a crate, E.g. https://lib.rs/crates/rprompt
- What is the impact of not doing this?
- Harder onboarding for beginners
- doing this in another crate may prevent it from going into rust book which beginners read
- No ergonomic for prototyping when user input is required
+ Standard library is able to force memory efficiency on users, but users could still use `Read` without `BufRead` which defeats the purpose
+ Standard library not bloated by one-liner input function which may not be used by many
# Prior art
[prior-art]: #prior-art
Discuss prior art, both the good and the bad, in relation to this proposal.
A few examples of what this can include are:
- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had?
- For community proposals: Is this done by some other community and what were their experiences with it?
- For other teams: What lessons can we learn from what other communities have done here?
- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background.
This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture.
If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages.
Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC.
Please also take into consideration that rust sometimes intentionally diverges from common language features.
---
Categorize different popular languages (or show one sample for each type?)
- one-liner
- python 3
- ruby
- no ergonomic
- go
- rust
- insane
- assembly
- brainfuck
http://rosettacode.org/wiki/User_input/Text
https://github.com/rust-lang/rust/pull/74178 (thanks @sHaDoW-54 to step up and send a pull request)
https://github.com/rust-lang/rust/pull/75435
https://crates.io/crates/rprompt
https://internals.rust-lang.org/t/add-plain-simple-way-in-the-standard-library-for-reading-values-from-stdin/10846
# Unresolved questions
[unresolved-questions]: #unresolved-questions
- What parts of the design do you expect to resolve through the RFC process before this gets merged?
- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
- 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?
- Is the rust book able to let beginners use a crate for stdin?
- If yes then this should ideally be done as a separate crate
# Future possibilities
[future-possibilities]: #future-possibilities
Think about what the natural extension and evolution of your proposal would
be and how it would affect the language and project as a whole in a holistic
way. Try to use this section as a tool to more fully consider all possible
interactions with the project and language in your proposal.
Also consider how this all fits into the roadmap for the project
and of the relevant sub-team.
This is also a good place to "dump ideas", if they are out of scope for the
RFC you are writing but otherwise related.
If you have tried and cannot think of any future possibilities,
you may simply state that you cannot think of anything.
Note that having something written down in the future-possibilities section
is not a reason to accept the current or a future RFC; such notes should be
in the section on motivation or rationale in this or subsequent RFCs.
The section merely provides additional information.