This RFC proposes the addition of macros or some functions that can be used to read input from the user in a more ergonomic way, similar to the Input built-in function in Python.
With this initiative, we can build a small interactive programs that reads input from standard input and writes output to standard output. This is well-established as a simple and fun way of learning and teaching Rust as a new programming language.
print!;
let possible_name: = input!; // This could fail, for example
// if the user closes the input
// stream
// Besides we can show a message to the user
let possible_age: = input!;
// This could fail, for example if the
// user enters a string instead of a
// number in the range of u8
// And yes, this is a result so we can handle errors like this
let lastname: String = input!
.expect;
// This could fail for example if the
// user enters a empty string
// --- Another way to use the macro ---
let price: Price = input!?;
// This could fail for example if the input is reading from a pipe and
// we delete the file whose descriptor is being read while the
// program is running
The examples above demonstrate several ways to use the input! macro.
In this macro, reaching EOF is consider an error case, so we return a Result
with an error type indicating the cause of the EOF. This approach makes error
handling straightforward and maintains consistent behavior.
Alternatively, the following can be used:
let name: = try_input!?;
In this case, EOF is not treated as an error, but as a valid case represented by
None. This allows handling EOF differently from other errors, since EOF
indicates the absence of a value rather than an error condition. This is why the
macro returns an Option type.
Note: The behavior of the input! is intended to be intuitive, but the
try_input! may be useful in cases where more nuanced error handling is
required. The name try_input! is provisional and open to change or removal
based on further discussion and feedback.
The behaviour of try_input! is similar to input! but its return
type is Result<Option<T>, InputError>. The InputError is a enum that
contains the error that could be returned by the input! macro. This design was
inspired by
commet of Josh Tripplet
on a previous RFC. Handling EOF
as a distinct case (rather than an error) allows for more flexible error
handling, as EOF is not always an error but rather a less common scenario that
can be overlooked.
Motivation
These macros could be especially useful for beginners, reducing the barrier to entry for new Rustaceans. They would also make the language more approachable and help lower the cognitive load when learning Rust.
For example, the second chapter of the Rust book introduces a guessing game and
demonstrates how to read input from the user. The current approach is not very
beginner-friendly and can be difficult to explain, especially concepts like
buffers. Using the input! macro would simplify this process and make it more
accessible.
This functionality is highly requested by the community, we can find as evidenced a PR in Rust with many comments and a lot of discussion about this topic. We can also find other RFC, another RFC and many issues in the Rust repository that discuss this topic. In addition, this is not a new idea, as we can find similar topics in internal.rust-lang discussions.
This RFC aims to provide a solution to this problem.
Like many others, I would like to have a simple way to read input from the user without having to deal with the complexities of the standard library.
This RFC proposes to allow us a more graceful introduction to Rust, not only a utility function. It aims to make the language more approachable and friendly for new users of the language.
The idea behind creating a new macro comes from these discussions. In many of these discussions, examples are shown of possible implementations, but most of them use macros to implement this functionality. The idea could be implemented as a function, too, actually an old poll shows that the majority wants the feature, the specific implementation is less important. The implementation of this RFC is a macro, but it could be changed to a function if the community prefers that way or we could have both options.
This poll was mentioned in this comment on a previous RFC. (Thanks to undersquire)
Besides, the idea of this implementation is solve too the problem of parsing
some types for example in other languages we have functions like nextInt() in
Java which is a function that reads an integer from the input stream and returns
it. We don't have this in Rust, we have to use the read_line function and then
trim the string and parse it into the type that we want.
And this is a very common use case, we sometime read a string from the input
but we want to parse it into a number or a struct.
If we repeat this process many times in our code, we have a lot of boilerplate code that we can avoid with this macro.
Guide-level explanation
Explaining the idea is not a rabbit hole, it's a very basic idea.
- We add a new macro
input!that can be used to read input from the user in a more friendly way. This macro returns aResult<T, InputError>. - We add a new macro
try_input!that can be used to read input from the user in a more friendly way but with a different behaviour, this macro return aResult<Option<T>, InputError>. - In both cases we must to accept a type
TwhereTis a type who implementFromStrtrait, so we can convert the input to the type that we want. - We must to specify the
InputErrortype who must to be a enum that have three variants:EOFthat is the error that we get when we reach the end of the input stream.Parse(e)that is the error that we get when we can't parse the input to the type that we want,eis the equivalent to a variable which type isFromStr::Err.Io(e)that is the error that we get when we have a IO error,eis the equivalent to a variable which type isstd::io::Error.
Reference-level explanation
use ;
/// A macro that:
/// - optionally prints a prompt (with `print!`).
/// - reads **one line** from stdin.
/// - returns `Err(InputError::Eof)` if EOF is encountered.
/// - returns `Err(InputError::Parse(e))` if the input cannot be parsed.
/// - returns `Err(InputError::Io(e))` if an IO error occurs.
///
/// # Usage:
/// ```no_run
/// // No prompt
/// let text: String = input!().unwrap();
///
/// // With prompt
/// let name: String = input!("Enter your name: ").unwrap();
///
/// // Formatted prompt
/// let user = "Alice";
/// let age: String = input!("Enter {}'s age: ", user).unwrap();
/// ```
/// A macro that:
/// - prints the prompt on its own line (with `println!`),
/// - then reads one line,
/// - returns `Err(InputError::Eof)` if EOF is encountered.
/// - returns `Err(InputError::Parse(e))` if the input cannot be parsed.
/// - returns `Err(InputError::Io(e))` if an IO error occurs.
/// - otherwise parses into `String`.
///
/// # Usage:
/// ```no_run
/// let line: String = inputln!("What's your favorite color?").unwrap();
/// ```
/// A single function that:
/// 1. Optionally prints a prompt (and flushes).
/// 2. Reads one line from the provided `BufRead`.
/// 3. Returns `Err(InputError::Eof)` if EOF is reached.
/// 4. Parses into type `T`, returning `Err(InputError::Parse)` on failure.
/// 5. Returns `Err(InputError::Io)` on I/O failure.
/// A unified error type indicating either an I/O error, a parse error, or EOF.
/// Defines how the prompt should be printed.
Another thing to consider is that the input! and inputln! macros just
delegate to the read_input_from function, which is the core of the
implementation. This function is generic over the reader type and the type
that we want to parse. Besides that, we have a PrintStyle enum that is used to
determine how to print the prompt. The PrintStyle enum has two variants:
PrintStyle::Continuethat is used to print the prompt without a trailing newline.PrintStyle::NewLinethat is used to print the prompt with a trailing newline. It allow us to use the same function for both macros and to have a more ergonomic way to print the prompt.
You can find the implementation in this repository or you can install it from crates.io with:
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.
Rationale and alternatives
Why should the macro trim newlines?
We assume that the returned string will often be processed with
FromStr::from_str, for example it is likely that developers will attempt the
following:
let age: i32 = input!?;
If input() 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 or a trim_end_matches 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.
This is the default behavior in Python and C# for give you a example, these
cases trim newlines by default. In Go, the bufio.Reader.ReadString() function
does not trim newlines, but the bufio.Scanner type does. The bufio.Scanner
type is the one that is used in the Go standard library for reading input
from the console. The bufio.Scanner type is a higher level abstraction.
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:
What is the impact of not doing this?
If this RFC is not accepted, the current way of reading input from the user
will remain the same. This means that new Rustaceans will have to learn how to
use the std::io::Stdin::read_line function and how to handle the Buffer
and the String types. This can be a barrier to entry for new Rustaceans and
it can make the language less friendly.
This RFC was presented as a pre-RFC at a Rust Argentina meetup, where it received positive feedback, particularly from attendees new to Rust (many with backgrounds in NodeJS, Python, and Go). They found the current approach to reading input in Rust complex and not very user-friendly, and were enthusiastic about the proposed macros.
They were not too happy with the current way of reading input from the user. They think that it was too complex and not too friendly.
The presentation can be found here (in Spanish) And yes, the guy with the black shirt with the Rust logo is me (👋).
Could this be done in a library or macro instead?
Well yes, but I think that this is a good idea to have it in the standard library. I think that this is a good idea to have it in the standard library because it is a common use case and it is a good idea to have it in the standard library to make the language more friendly.
Another way that we could consider this is added like a pseudo oficial library
like rand which is not in the standard library but is recommended by the Rust
team and the oficial documentation as the book does it.
Prior art
Python has input(), Ruby has gets, C# has Console.ReadLine()
... all of these return a string read from standard input.
Some behaviors are different, for example:
- Python's
input()returns a string, but if the input is EOF it raises anEOFErrorexception. - Ruby's
getsreturnsnilif the input is EOF. - C#'s
Console.ReadLine()returnsnullif the input is EOF. - Go's
bufio.Reader.ReadString()returns an error if the input is EOF. - Java's
readlnreturns annullif the input is EOF.
Maybe a thing to have in mind is that in JavaScript we have the prompt function
but this function in the case of the browser enable a little dialog when we
select cancel this dialog we receive a null value.
This is a specification in JavaScript.
Unresolved questions
What parts of the design do you expect to resolve through the RFC process before this gets merged?
Should the function additionally be added to std::prelude, so that beginners
can use it without needing to import std::io?
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.
In the current version of the book, the code looks like this:
let mut guess = Stringnew; stdin .read_line .expect; let guess: u32 = guess.trim.parse.expect; println!;With the new macros, it could be simplified to:
let guess: u8 = input!.expect; println!; -
Clippy might also introduce a lint to tell users to avoid unnecessary allocations due to repeated
inputln()calls and suggeststd::io::Stdin::read_lineinstead.
With this addition Rust might lend itself more towards being the first programming language for students.