Skip to content

RFC: input macros #3799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open

Conversation

Phosphorus-M
Copy link
Contributor

@Phosphorus-M Phosphorus-M commented Apr 19, 2025

This RFC proposes adding ergonomic input!, inputln! and may try_input! macros to Rust for reading user input, inspired by Python’s input().
These macros simplify input handling, improve beginner experience, and provide clear error handling for EOF, parse, and I/O errors.

The goal is to make Rust more approachable for newcomers and teaching.

Previous discussions:

This RFC is a compilation of comments and feedback provided on previous RFCs, especially RFC #3196 .

Rendered

@jieyouxu jieyouxu added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Apr 19, 2025
@ahicks92
Copy link

The alternative I don't see here is why this can't just be a more friendly line-reading function. The macros here add very little. Teaching someone to do:

print!("The prompt ");
let mystring = std::io::convenient_read_line().unwrap();

Seems like the better way unless the macros are going to handle parsing too (like C scanf, not just delegating to FromStr). The macros here seem to be about getting around Rust's lack of function overloading, which is kind of an anti-pattern--just teach that Rust doesn't have function overloading if it's that relevant.

Ultimately there is a distinction in Rust: a macro is spitting out code which must be in the caller, parsing parameters so complex that you can't do it by hand, or doing something like printing where the compiler must understand what's going on. There's lots of exceptions, but that rule usually holds. This breaks it and it's not evident why.

On the one hand there's no harm in this but on the other "Rust does input weirdly because it is a systems programming language which means..." seems kind of like the point here.

I sort of object to hiding the conversion as well. I imagine that the idea here is that you then don't have to teach FromStr exactly but Rust isn't about hiding details for the sake of hiding details. Even with that though, you still have to teach it at the first compiler error; it's leaky. A function at least makes it obvious there's a generic going on where a macro doesn't.

Another important alternative: do this via a type, not a function, and have that type hold the buffer. I suppose this is what BufRead is doing.

There is also stdin.llines() which does provide an easy way to do per-line reading today. I don't do per-line reading often but for line in std::io::stdin().lines() { ... } I believe. It's not required for every possible input scenario to be easy in stdlib, IMO.

@Phosphorus-M
Copy link
Contributor Author

The alternative I don't see here is why this can't just be a more friendly line-reading function. The macros here add very little. Teaching someone to do:

Yes, that’s true, some have suggested it could be a function instead of a macro.
We could include both if there’s consensus, but I’m initially proposing a macro to stay consistent with other Rust utilities like println!, assert!, or vec!.

The macros here seem to be about getting around Rust's lack of function overloading, which is kind of an anti-pattern--just teach that Rust doesn't have function overloading if it's that relevant.

Rust not supporting function overloading shouldn’t mean we offer less ergonomic APIs.
This macro is intuitive, idiomatic, and easy to implement.
Personally, when I was learning Rust, something like this would’ve helped me a lot, here’s an example, a friend (@LeoBorai) actually helped me with this back then, and I ended up creating this:
A utility to parse data from the stdin

I’ve talked to many beginners who run into the same issue.

On the one hand there's no harm in this but on the other "Rust does input weirdly because it is a systems programming language which means..." seems kind of like the point here.

Currently, I don’t consider Rust to be strictly a systems programming language. In fact, the official slogan has changed to
"Rust is a language empowering everyone to build reliable and efficient software"
which includes systems programmers, but is not limited to them.

We’re not trying to hide read_line, just add a simple abstraction to improve usability, especially for early learners.
Of course, for complex input handling, we must to recommend more advanced tools like a manual use of stdin.

This RFC is mostly a summary of past proposals. There’s been debate about whether it should be a function or macro, and about return types.
I went with a macro for ergonomics, but I’m open to it being a function if that’s preferred.
I just assumed a macro was better because the topic has been discussed a lot, and macros are always used as examples.

@clarfonthey
Copy link

I would personally argue that combining the printing of a prompt and the actual input is not more ergonomic: those are two fundamentally different things, and combining them into one function falsely conflates the two.

Sure, it's common to do both in quick succession, especially for beginner-level examples, but that just means we should make both easy to do individually, not that we should combine them.

It really doesn't feel like input! offers a substantial benefit over print! followed by a simple input() function, and that we should focus on offering a simple input() function instead.

@Noratrieb
Copy link
Member

Noratrieb commented Apr 20, 2025

Since there is a bit of design space here regarding macros/functions, EOF, errors, I think it's very important to make the Motivation section way larger, focusing on different use cases of where someone would want to use this function and where today's options are not sufficient, and then derive the specific desired behavior from there. There isn't that much reasoning on why the choices were made, but this is by far the most important part of any RFC.

@Phosphorus-M
Copy link
Contributor Author

Sure, it's common to do both in quick succession, especially for beginner-level examples, but that just means we should make both easy to do individually, not that we should combine them.

Right, maybe it's not such a good idea, but it's a very common pattern. For example, in Python and JS, you can add a message
I think it will also help the fact that It share a similar API with those languages.

Since there is a bit of design space here regarding macros/functions, EOF, errors, I think it's very important to make the Motivation section way larger, focusing on different use cases of where someone would want to use this function and where today's options are not sufficient, and then derive the specific desired behavior from there. There isn't that much reasoning on why the choices were made, but this is by far the most important part of any RFC.

That's true, you make a good point, I'll polish the RFC a bit more
I didn't want to be redundant in some things

Thanks

@abgros
Copy link

abgros commented Apr 20, 2025

I strongly support a combined prompt + input macro. Getting input without a prompt is meaningless because the user will have no idea what to type. Whether they are different "things" doesn't matter because we've already established that the goal is to create a convenience macro, not a robust API for any input-related situation.

Copy link
Member

@jieyouxu jieyouxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some elaboration questions, but I don't really have a horse in this race (as in, I don't really foresee myself using this API). So not really blocking concerns from me. Just some .oO shower thoughts.

Comment on lines +248 to +256
pub fn read_input_from<R, T>(
reader: &mut R,
prompt: Option<Arguments<'_>>,
) -> Result<T, InputError<T::Err>>
where
R: BufRead,
T: FromStr,
T::Err: std::fmt::Display + std::fmt::Debug,
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion: so this API as proposed might be somewhat limiting:

  • This API requires the "parsed type" T to implement FromStr. However, it's possible that there isn't a canonical textual representation for the type T.
  • Additionally, this API requires that the associated error type implement std::fmt::{Display,Debug}, which I imagine is also sometimes limiting, because a user might not want to impl std::fmt::{Display,Debug} for their error type for various reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Maybe it's not the best option—some suggested it should be a TryFrom instead of FromStr. I suggested using FromStr, as mentioned in previous discussions, just to follow the library's style. We could use TryFrom, but it would require more changes (I think).
    For example, std::parse uses FromStr specifically for parsing.

  • Maybe we could change the error type to std::error::Error, but it would be somewhat similar since it also implements std::fmt::Display and std::fmt::Debug. It might be a bit limiting to use Display and Debug, but I don't think it would be an issue for the intended use cases (I think).

In any case, what do you think about it? Do you have any suggestions on how we could handle it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't really have alternative designs, since I don't really have a use case for this API. I just wanted to point out potential limitations (which are not necessarily problematic if they are intentionally chosen as tradeoffs).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This API requires the "parsed type" T to implement FromStr. However, it's possible that there isn't a canonical textual representation for the type T.

This new feature is a convenience method.

It is almost entirely the composition of Stdin::lock, BufRead::read_line, str::strip_suffix('\n') and FromStr::from_str.

If you want something different (your type doesn't implement FromStr) then you can call the underlying functions instead.

  • Additionally, this API requires that the associated error type implement std::fmt::{Display,Debug}, which I imagine is also sometimes limiting, because a user might not want to impl std::fmt::{Display,Debug} for their error type for various reasons.

I think this is only needed for the InputError type to implement std::error::Error. In principle that trait impl could be made conditional, by moving the bounds there. I'm not sure that's a good idea.

Comment on lines +259 to +266
PrintStyle::Continue => {
// Use print! for no newline
print!("{}", prompt_args);
}
PrintStyle::NewLine => {
// Use println! for adding a newline
println!("{}", prompt_args);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion: I know this is illustrative, but what is the intended behavior if stdout is a broken pipe? Or if there's some other IO errors when trying to write the prompt to stdout?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good observation. The current RFC doesn't specify how such stdout write errors should be handled. Honestly, I'm not sure how println! does it—I'd guess it panics? I'm not sure how we could tackle that issue, and I don't really know how to reproduce such a case either.
I tried running something like cargo run | head -n 1, but I couldn’t reproduce it. Maybe you can think of something else we could try to trigger it?

Either way, we could try something like this:

writeln!(io::stdout(), "{}", prompt_args).map_err(InputError::Io)?;
write!(io::stdout(), "{}", prompt_args).map_err(InputError::Io)?;

Which would let us handle the write error on stdout instead of panicking.

If that sounds good to you, we could add it to the RFC and see what others think.

Copy link
Member

@jieyouxu jieyouxu Apr 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK println! will panic on broken pipes. I think that change looks reasonable since it at least allows the user to decide what to do about IO errors while writing to stdout. But then again, I'm not the best person to judge what's the best behavior here since I don't really have a vision on the use case.

MCVE for broken pipe: try

// hello.rs
fn main() {
    println!("hello world");
}
$ rustc hello.rs
$ ./hello | echo

thread 'main' panicked at library/std/src/io/stdio.rs:1123:9:
failed printing to stdout: Broken pipe (os error 32)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Comment on lines +272 to +273
let mut input = String::new();
let bytes_read = reader.read_line(&mut input).map_err(InputError::Io)?;
Copy link
Member

@jieyouxu jieyouxu Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion: I know this is illustrative too, but this doesn't limit how much input bytes (or UTF-8 characters, I suppose) will be read. For instance, I think it's possible to keep sending input without a newline/EOF.

In this current proposed signature, or through the current forms of the input!() and try_input!() macros, there isn't a way for the user to specify an upper limit in bytes of some kind of input line buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the current design aims to prioritize simplicity and ergonomics for common use cases. But we could extend the API in the future (e.g., input_with_limit!(limit, prompt)) or allow configurations through a builder if the need becomes frequent. Maybe it would be worth mentioning that possibility in the RFC?

I don't think it's such a typical use case in fact, I believe no one has brought it up until now—but in any case, we could propose it as a separate macro or function, similar to how println! and eprintln! work, where different macros are used for different use cases.

Copy link
Member

@jieyouxu jieyouxu Apr 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth spelling out what kind of use case you are envisioning for this API. For instance, not being robust against infinite input is a totally acceptable trade-off in some scenarios like toy programs or say small script-like programs, but is very much not acceptable in other scenarios.

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
Copy link
Member

@jieyouxu jieyouxu Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remark: AFAICT this RFC in its current form is slightly light on how it evolved to the current design -- you backlinked a lot of discussions in the PR description (which are of course very helpful), but I'm not sure if alternative proposals were discussed in terms of their tradeoffs, and how it's determined that this design has the best tradeoffs in your opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for noticing that. Indeed, there were several alternatives discussed in the linked issues, but you're right we could do a better job summarizing how we arrived at the current design and why we consider it the most balanced.
To summarize: this is currently the simplest design, and one of the most frequently proposed in the discussions (this has been suggested since 2014, lol). I think if everyone keeps arriving at the same design (even without reading all the discussions), it’s probably because it’s the most intuitive one.

Something similar happened in Python, where the function name input was initially rejected and other alternatives were considered, but they eventually went with input.

Comment on lines +394 to +412
## 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](https://rust-lang.ar/p/2025-april/),
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](https://youtu.be/CjZq93pzOkA?t=4080) (in
Spanish)
And yes, the guy with the black shirt with the Rust logo is me (👋).
Copy link
Member

@jieyouxu jieyouxu Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion: Personally, I don't find this argument super convincing. For instance, here are some ecosystem alternatives instead of adding this to the standard library:

(Note that I'm not really expecting you to compare all of these, this listing is just advisory in terms of "what are some of the alternatives out there?")

Notably, observe how the APIs between these ecosystem crates can be quite different, since the crates naturally tailor to different use cases (sometimes overlapping).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree, and in fact that diversity shows just how useful this feature is for the community. The motivation for including something minimalistic in the standard library is to provide a good-enough solution for the most common cases, without requiring new users to research and choose a crate. It's not meant to replace the flexibility offered by third-party solutions.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a good idea to test out any proposed API like this as a crate on crates.io first. It's much easier to evolve APIs on crates.io than it is in the standard library, and that way you could get feedback from users on stable rust (rather than asking new learners to install nightly and learn about how to enable unstable features)

And I do think getting feedback from users is helpful for showing that the API is clear and a good fit for the expected usecase.

At least as far as I can tell, your proposed API isn't already published as one of the above-listed crates; if it already is then that would certainly be worth mentioning in the RFC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As related work you can take https://crates.io/crates/text_io which does implement such macros and appears to be used regularly considering ppl open PRs and issues every year

@ahicks92
Copy link

@Phosphorus-M
I won't really be around here a lot because I don't have much of a stake and am very busy, but I have a moment right now.

If Rust isn't a systems programming language that's news to possibly everyone? Maybe slogans changed and indeed Rust is good at "being Go" if you will--I don't have a better term for that weird niche. But in terms of design everything has been and probably will always be about the systems programmer first. You can't not, because every time you do something else you've put more stuff beyond the reach of all of those using Rust on things without an OS, and in many cases also beyond the game dev crowd who care about things like no allocation for different reasons but often end up with similar requirements. This is a big part of the async design, which could be much more ergonomic at the cost of having to have a language-blessed runtime and require allocations. There is a reason fallible allocation and custom allocators come up and are being actively worked on, , the stuff for RFL, etc. The debate can be had that this macro should not care about these things, but print etc. do actually care to the point of applying compiler optimizations specifically for them, and don't necessarily even allocate under the hood. You can run the write macro in no_std I believe, even though it very much looks like you shouldn't be able to.

Rust wants to be learnable, but that's not the primary concern. At a point "Just use Python" is a fine response. I strongly suggest to people that Rust not be their first language even though I love it. This is trying to drain the ocean with a bucket. I wouldn't want to give students Rust because that's a lot of cognitive load out the gate that they don't need. IMO anyone who is at the point of being able to handle Rust can handle typing cargo add foo as needed.

You are also sort of correct on the style matching the print family of macros, at least at face value. But to me and I suspect many others, the "style" there is that those macros are doing things that you can't otherwise do in the language. This comes back to what I mean about when macros are used. Even as presented your macro doesn't do anything that justifies being a macro. When people see macro invocations the first question is "why is this special?" and the answer here is it's not, and that is unique among the other set you're saying this belongs in. Now, if I could do:

let x: f64;
let y: f64;
input!("{x}, {y}");

That would justify it and be really cool. Indeed read! macros like that would be extremely neat. But my point is crossing that line crosses the line of being special where this doesn't. I've never seen a "this is a function" macro ever to be honest.

Now all of this said I do spot one other thing here that's possibly going to make this dead on arrival if it's a macro. What about the prelude? input is an extremely generic identifier. I'm not sure what happens if you have a macro in the same scope as a function and/or variable of the same name but you may wish to check on that. The fix might be as simple as putting it in a module, which can be done on stable with a lot of hoops and which stdlib does have already...but you'll need to consider that.

You may feel like I'm picking on you so let me be clear: adding this doesn't actively do harm and if people really want it that's fine. These are the problems I have with it and the reasons why in the hypothetical world as a decision maker I'd say no to this as is. The design is fine, it's just not shaped like Rust.

@programmerjake
Copy link
Member

programmerjake commented Apr 22, 2025

I'm not sure what happens if you have a macro in the same scope as a function and/or variable of the same name but you may wish to check on that.

Since input is in the macro namespace, but not in the type or value namespaces, nothing happens. function names/variable names (both in the value namespace) simply don't see input as something that exists.

Additionally, even if input was in the same namespace, things from the prelude have the lowest priority, so things you define yourself will override anything of the same name from the prelude (the only exception I'm aware of is associated functions/types in traits, hence why adding traits to the prelude requires a new edition).

(edit) AFAICT the closest thing to docs: https://doc.rust-lang.org/stable/nightly-rustc/src/rustc_resolve/ident.rs.html#57-96

Comment on lines +70 to +74
Alternatively, the following can be used:

```rust
let name: Option<Price> = try_input!("Please enter a price: ")?;
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this adds enough value to keep in this proposal. Handling EOF specially can already be done by checking for the proposed InputError::Eof, or by using the underlying primitives.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I had the same impression. I added it just for the sake of correctness and because there were some comments about it.
If you agree, this idea could be discarded in my opinion.
I don't think it's a bad idea in those terms, but at the same time, I feel it wouldn't add enough value and could complicate the discussion.

Comment on lines 19 to 22
println!("Please enter your name: ");
let possible_name: Result<String, _> = input!(); // This could fail, for example
// if the user closes the input
// stream

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am definitely in favour of having some simple way to do this task, that makes the easy case easy.

A feature like this has three uses:

  1. It can be used in documentation and examples, where it is good to avoid distractions
  2. It is useful in real programs when the focus of the program is not "having a sensible CLI" but rather doing some complex work.
  3. It can be a stepping stone to help the very new Rust programer,

For uses 3 and 1 it is very important that the new thing has a very simple API. Anything that's not necessary for this task should be eliminated.

I think that means eliminating prompt-printing. If the new input facility doesn't print prompts, it doesn't need to handle errors while printing them; it doesn't need to optionally take a prompt. The result is a very simple feature, which is a straightforward composition of existing primitives.

I agree with those who say this shouldn't be a macro. In the current proposal it only has to be a macro to allow both the calling conventions: with a prompt, and without. A more Rustic way to do that would be to use Option, or have two functions, or something. But we don't want to complicate the API. Note that an optional argument (which doesn't otherwise usually appear in Rust) is itself a complication.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there have been several messages regarding removing the prompt. Maybe we could create a new poll or use the one that already exists, since it’s still open. In case we go for a new one…
Maybe three options are enough?

  • Turn it into a function without parameters
  • Keep the macro and the prompt
  • Remove the prompt but keep the macro

Comment on lines +248 to +256
pub fn read_input_from<R, T>(
reader: &mut R,
prompt: Option<Arguments<'_>>,
) -> Result<T, InputError<T::Err>>
where
R: BufRead,
T: FromStr,
T::Err: std::fmt::Display + std::fmt::Debug,
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This API requires the "parsed type" T to implement FromStr. However, it's possible that there isn't a canonical textual representation for the type T.

This new feature is a convenience method.

It is almost entirely the composition of Stdin::lock, BufRead::read_line, str::strip_suffix('\n') and FromStr::from_str.

If you want something different (your type doesn't implement FromStr) then you can call the underlying functions instead.

  • Additionally, this API requires that the associated error type implement std::fmt::{Display,Debug}, which I imagine is also sometimes limiting, because a user might not want to impl std::fmt::{Display,Debug} for their error type for various reasons.

I think this is only needed for the InputError type to implement std::error::Error. In principle that trait impl could be made conditional, by moving the bounds there. I'm not sure that's a good idea.

@matthieu-m
Copy link

I can't find a good justification for it being a macro.

Yes, some language have this combined prompt function, but the alternative is far from overbearing:

print!("Input your guess: ");
let guess = std::io::input()?;

In fact, this "saves" us from having to standardize both an input! and inputln!, the latter of which isn't necessarily clear as to where the newline implied by ln would go... whereas if I use print! and println! prior to calling input then it's dead clear.

Note that in most cases, the users will need to be acquainted with print! and println! anyway, so it doesn't simplify the learning process to have a combined "prompt+input" function on top.


One motivation for having input is, rather than just reading a line from Stdin and calling parse on it, besides the boilerplate... is the required synchronization between stdout and stdin.

That is, it's important to flush stdout prior to reading from stdin so the user sees the prompt, and is not left guessing what's asked of them, and it's perfectly fine for a non-performance sensitive function like input() to always flush, without wondering if there's anything to flush in the first place.

@tmccombs
Copy link

print!("Input your guess: ");
let guess = std::io::input()?;

This example actually illustrates why combining them is useful, because this simple example is actually incorrect.

print! doesn't flush stdout, and stdout is usually line buffered, so this will end up waiting for user input before the prompt is shown.

Maybe the input function could flush stdout before waiting for input, but that feels weird to me.

Another option is to add a new variant of print, either a new macro, or an option to the existing macro, that does flush.

@abgros
Copy link

abgros commented Apr 24, 2025

I maintain that getting input without a prompt is nonsensical. It's a use case that doesn't exist.

@programmerjake
Copy link
Member

I maintain that getting input without a prompt is nonsensical. It's a use case that doesn't exist.

that's not true, many programs don't prompt before trying to read input, e.g. cat, sort.

Comment on lines +332 to +334
* 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation could potentially use a thread local String, or even a global Mutex<String> as a re-useable buffer to avoid allocating a String for every call.

However, note that if T is String the call to parse will still allocate a new String (the currently implementation actually allocates two Strings in this case, since the parse call will make a copy of the original string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a different implementation in mind, or could you provide an example of your idea? It's just to have a clearer understanding of how it would work.

@Aloso
Copy link

Aloso commented Apr 25, 2025

print! doesn't flush stdout

This was my first thought as well.

It's not a problem if std::io::input() calls std::io::stdout().flush() before reading from stdin. However, this would make it a leaky abstraction (for example, it wouldn't work with eprint!), and it would be quite surprising that when replacing std::io::input() with Stdin::read_line, the text is no longer printed.

@matthieu-m
Copy link

matthieu-m commented Apr 25, 2025

print!("Input your guess: ");
let guess = std::io::input()?;

This example actually illustrates why combining them is useful, because this simple example is actually incorrect.

No, it doesn't.

The second part of the comment precisely explains that you need to have input() call flush on stdout.

print! doesn't flush stdout

This was my first thought as well.

It's not a problem if std::io::input() calls std::io::stdout().flush() before reading from stdin. However, this would make it a leaky abstraction (for example, it wouldn't work with eprint!), and it would be quite surprising that when replacing std::io::input() with Stdin::read_line, the text is no longer printed.

I'll disagree, thrice.

input doesn't exist yet, so it's up to this RFC to establish its usecase and its functionality.

My premise is that input's raison d'etre is interactive user-input. From there:

  1. input must ensure that stdout is flushed, lest the user is left without the prompt about what to input.
  2. input's performance is not critical, the user-input will be the bottleneck.

First, I disagree with the fact that replacing input with Stdin::read_line will not flush stdout implicitly any longer should be seen as a problem. This means that programs which need to read from stdin in a non-interactive manner -- piping is common for Unix-style utilities -- can use Stdin without performance penalty.

Second, I disagree that not flushing stderr should be seen as a problem. That's normal: stderr is not for regular program output. Also do note that as per Wikipedia's Standard Streams, in general stderr is unbuffered and therefore there should be no need to flush in the first place. If the presence of input() only flushing stdout drives beginners to correctly print their prompt on stdout, I see this as a step in the right direction w.r.t teaching them standard stdout / stderr etiquette.

Third, I therefore disagree with input being a leaky abstraction. It's an abstraction built for a specific purpose, gathering prompted data from the interactive, with ease. In order to do so, it bundles together a few pieces of functionality: flushing, reading from stdin, and finally parsing. And I would argue that's fine.

I would also note how easy it is to write a prompt! (or input!, it's a separate macro) on top of it:

macro_rules! input {
    () => { std::io::input() };
    ($($token:tt)+) => {{ print!($($token)+); std::io::input() }};
}

Proving that input() is a good primitive, allowing to easily build atop of.

Playground link for a quick demo.

@matthieu-m
Copy link

@Phosphorus-M Isn't there a buffering issue?

One of the known difficulties in handling the standard streams is that there's no take back.

This makes me wonder if the current implementation, in terms of BufRead doesn't run into an over-read issue:

  • Program prompts the user.
  • User enters first line.
  • User enters second line.
  • Program buffers everything on stdin, reads (& parse) first line, moves on.
  • Program prompts the user again.
  • Program waits for answer to second prompt, having discarded the previous buffer.

I note that there's an inherent Stdin::read_line method which may not suffer from this buffering issue? It's really not clear to me, and could throw a wrench in the plans.

@Phosphorus-M
Copy link
Contributor Author

Sorry for the delay, during the week I’m usually more focused on my work

@ahicks92

My input! proposal does not intend to replace Rust’s low-level philosophy or compromise its control model. Instead, it aims to offer a lightweight ergonomic shortcut for contexts where maximum low-level control is not critical: prototyping, learning, simple CLI tools, and exercises.
While this macro is intentionally simple now, it could evolve into a much more powerful feature, offering safe, typed input parsing, filling a real ergonomic gap for Rust users today.

let x: f64;
let y: f64;
input!("{x}, {y}");

You gave an example, and it could be similar if I simply implemented FromStr for, say, (f64, f64), or I could create a type to represent it, right?

struct CartesianCoordinate(f64, f64);

impl FromStr for CartesianCoordinate {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split_whitespace().collect();
        if parts.len() != 2 {
            return Err("Invalid input format. Expected 'x y'.".to_string());
        }

        let x = parts[0].trim().parse::<f64>().map_err(|_| "Invalid x coordinate".to_string())?;
        let y = parts[1].trim().parse::<f64>().map_err(|_| "Invalid y coordinate".to_string())?;

        Ok(CartesianCoordinate(x, y))
    }
}
let CartesianCoordinate(x, y) = input!()?;
println!("x: {}, y: {}", x, y);

It's just an idea.

I respectfully disagree with the notion that there must be a special macro-worthy justification for something as basic as reading user input. Sometimes ergonomic basics are their own justification.

Similarly, while Rust is a complex language, I do believe it can absolutely be taught as a first programming language, much like how C often was.
I personally started with C at university, and simple tools like scanf were a huge help early on.

Some today say that "Python is better for beginners and C is too hard" but the truth is that both languages can be beginner-friendly when taught appropriately, may ignoring complexity at first, and introducing deeper ideas gradually.

Likewise, input! would not make students forget the real workings underneath, it would simply let them take their first steps without overwhelming ceremony.

However, this poll is still open and I see several comments against using a macro, so we could either vote again here or create a new one

@Phosphorus-M
Copy link
Contributor Author

Phosphorus-M commented Apr 26, 2025

@matthieu-m

I think you make some good points. I believe you're right about many of them. As I said in other comments, we can run a new poll to settle on a final API or we can use the one that already exists. I think you bring up good arguments.
Part of the reason I was thinking about a macro, just like many others is because it’s easier to grasp: "input is a macro, output is a macro," understanding this in the sense of input! and print! macros.
I feel it's easier to grasp if it's a macro, but it's true that it might not be necessary.


EDIT: I read the other message about stderr.

Totally, I think this comment is very useful because it's something that hasn't been discussed before, and I think I agree with it. Initially, I presented it that way, as it's in the playground.
I modified it because I felt that if there was a need to differentiate between inputln! and input!, it would end up being too complex. It was easier to group everything into a single function, but the playground you showed is quite clear in my opinion.
On the other hand, the way you present it might simplify this discussion about whether to use macros or functions. This way, both cases could be provided if needed (which, in fact, was the most voted option: having both macros and functions).
On the other hand, I've seen many arguments that this would mix responsibilities. Do you have any thoughts on that?

@matthieu-m
Copy link

On the other hand, I've seen many arguments that this would mix responsibilities. Do you have any thoughts on that?

Syntax is bike shedding.

I can see a number of unresolved questions in the core functionality:

  • Is FromStr the right trait to require? It has a nice symmetry with Display, but Display and FromStr are only implemented for a small selection of types. Notably missing being Option, the collections, and the tuples.
  • Is using Stdin::read_line sufficient to avoid over-buffering? (and then losing the over-buffered input)
  • Which characters should be trimmed for the start and end of the line prior to proceeding to calling FromStr? Or is the trimming an indication that FromStr isn't the right trait after all?
  • Should structured input parsing be designed first, and only then retroactively applied to input? If a solution (and trait) for structured input -- say, scan! -- were implemented, would it be odd for input to be different? Also isn't there a potential conflict for input!("Prompt: ") (write prompt, then read input) and input!("{x} {y}") (structured input read)?

I would advise focusing the discussion on resolving those questions, and only then, start the bikeshedding of how to present the solution to the user: function and/or macro? name? ...

@nielsle
Copy link

nielsle commented Apr 29, 2025

It looks useful, but perhaps it should start out as a third party crate.

Comment on lines +24 to +28
// Besides we can show a message to the user
let possible_age: Result<u8, _> = input!("Please enter your age: ");
// This could fail, for example if the
// user enters a string instead of a
// number in the range of u8
Copy link
Member

@workingjubilee workingjubilee May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not wish to seem overly critical, but while some people said that printing the prompt and awaiting input should not be combined, I did not find anyone offering this exact reasoning. so I feel compelled to raise it.

a reader might call the examples for prompts in this RFC "toy examples" given that they do not exercise Rust's ability to use formatting arguments to the print! macro in order to generate complex prompts with highly dynamic formatting. but of course, "generate a complex prompt by formatting dynamic information" is something that actual toys... by which I mean games, including text-based ones, even very simple text-based games that are often the final exercise of a "how to program in $LANGUAGE" guide... often have wanted to do... while also waiting for user input.

the proposals for extending it with "structured input" using syntax that resembles that of our formatting macros would naturally conflict with making the prompt likewise complex and allowing formatting arguments. and people would naturally expect something that looks like a print macro to mostly work with various formatters, I would think?

so, as it is generally good, in PL design, to "make easy things easy and make hard things possible", then whatever the API becomes, it seems like conflating input and printing would be a categorically bad move. one might be inevitable with the other, but they should probably remain visibly distinct, even if they chain together in some way. builder patterns exist, after all.

it is possible there is some syntax that is so good that it allows combining these two concerns into a single macro. I will not pretend to know what that syntax is. I am trying to comment on how to use the existing API and its pedagogic utility (as pedagogic utility is highly emphasized in this RFC). I think it would be nice if its convenience didn't immediately implode on coming into contact with a new student's imagination, and that it worked in a way that strongly resembled other APIs already offered by the language, so anything involving a highly extended syntax, even via macro, seems like an unnecessary "feature creep".

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I raised the concern earlier (4th bullet point in #3799 (comment)), but not certainly not as extensively!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I may have missed that or arrived at a similar conclusion that it needed to be hammered on a bit more.

@Phosphorus-M
Copy link
Contributor Author

Sorry for the delay in responding. This will be a bit longer than usual, as I have a lot of things to say. I will try to be as concise as possible.
In response to you, @matthieu-m, this is what I think:

1.

Yes, FromStr is the most natural and idiomatic choice for basic input parsing in Rust. It aligns nicely with Display, providing a simple symmetry for both input and output types. While it’s true that FromStr is not implemented for all types (like Option, collections, or tuples), this RFC is targeting basic interactive user input, where the vast majority of inputs are primitive types or simple custom structs, exactly the types where FromStr is most commonly used.

FromStr allows users to define custom parsing for their own types, so it's extensible without the macro needing special knowledge about user-defined types.

Additionally, in future iterations we might consider the possibility of implementing FromStr for slices, tuples or for Vec<T> (e.g., comma- or space-separated inputs), but such a decision would require its own dedicated RFC. That would allow richer input formats without compromising the simplicity of this initial proposal

Regarding types like Option, slices, or tuples: there isn't an obvious or universally correct way to parse them from a string. Should Option<T> treat empty strings as None and anything else as Some(...)? Should a slice or tuple require a specific separator, or enforce size at runtime? These are valid but complex design questions that deserve their own focused discussions. For now, we believe it's appropriate not to address them within this RFC, and instead keep the scope clearly on types that already implement FromStr.


2.

Yes, Stdin::read_line is sufficient and safe to use for line-based input without risk of over-buffering and losing subsequent input, thanks to the way std library handles buffering internally.

You can safely use Stdin::read_line (or the higher-level BufRead interface) without worrying about input being buffered "too eagerly" and lost. The standard library takes care of buffering and state management, and multiple read_line calls will return consecutive lines correctly.

If you want low-level control, BufReader is still available and customizable, but for 99% of command-line use cases, Stdin::read_line is the right way.


3.

We should only trim the trailing \r and \n characters before calling parse from FromStr. These characters are naturally introduced by pressing Enter and are not meaningful for the value being parsed, removing them helps maintain predictable behavior without affecting the intended input.

Trimming the trailing \r and \n before parsing is a very standard behavior for interactive inputs. It's what users naturally expect when typing data manually.


4.

Structured input (e.g., scan!) and simple value input (input!) serve different use cases.

In my opinion, input! should follow a simple design, focused on a single line and a single value at a time, similar to how Python’s input() works, or other common input variants in other languages. However, it should also be able to parse values—not just return a String, but also support numeric types, for instance, and provide a way (a trait, in this case FromStr) to parse custom user-defined structures.

We could later iterate on this idea, as @workingjubilee suggested, using a builder pattern or something similar, but I think that would reduce the ergonomics and simplicity that we’re aiming for.

In another iteration, we could introduce something like scan!, which would no longer be as simple, but could support positional and formatted reads, as some comments suggest.

We could also consider evolving input!, assuming it should be a macro rather than a function. In that case, there could indeed be a conflict between the declared type and the input format. Still, I think making it a macro might not be a bad idea because it would allow us to apply some of the parsing logic discussed in future iterations.

I can already think of at least four ways such a macro could evolve:

let something: u8 = input!(); // A single-line input, parsed using FromStr <-- our current scope

And in the future, something like:

let something_else: (i32, bool) = input!("{} {}"); // Multiple inline values
let something_else2 = input!("{x:i32} {y:bool}"); // A record-like structure with fields x and y, like sqlx's query! macro
let something_else3: (i32, bool) = input!("{},{}"); // Split using ',' as delimiter
let something_else4 = input!("{y:bool},{x:i32}"); // Same, but re-ordered fields, forming an anonymous record

That said, this might not be within the scope of this RFC, we’re aiming for the simplest use case.

With that in mind: yes, I’m open to removing the prompt part. It’s true that it mixes responsibilities and may not be the best idea. Removing it would also open the door to exploring more powerful parsing strategies in future RFCs.

@Brayan-724
Copy link

@matthieu-m Thank you for raising these important questions. I’d like to share my thoughts on each point before we move on to naming and ergonomics:

  1. FromStr is designed for types that can unambiguously parse themselves from a single, contiguous string. While it’s conceivable to implement FromStr for Option<T>—for example, treating the empty string as None and "x" as Some(x)—extending it further to collections or tuples would require imposing ad-hoc delimiters and edge-case rules that don’t feel natural for all users. By limiting input!() to types with existing FromStr implementations in std, we keep the API surface small and predictable. Users who need richer parsing semantics (collections, multi-field inputs, etc.) can layer on a separate helper crate rather than inflating this macro’s responsibilities.

  2. (buffering discussion omitted)

  3. It’s important to strip off the trailing \n (and, on Windows, \r\n) before invoking FromStr, because most existing implementations will reject any unexpected whitespace. By performing a minimal .trim_end() (or equivalent), we avoid invalidating the current ecosystem of parsers while still presenting the user with a clean string to parse.

  4. The goal of input!() is a lightweight “print prompt → read line → parse” workflow. Overloading it to handle multi-field or pattern-based scanning would complicate both the user interface and the implementation. Keeping the macro focused on a single prompt and parse step ensures it remains easy to understand and maintain.

Once these foundational behaviors are settled, we can move on to ergonomics—macro vs. function, naming, error-reporting style, and so forth. I look forward to everyone’s feedback on these core design choices!

@Phosphorus-M
Copy link
Contributor Author

@Brayan-724

Right, I agree with everything except the idea of implementing FromStr for Option<T>. I think that goes beyond the scope of the RFC. I don't think it's a simple discussion, would a blank space be a None? I say this because it's easy for basic types, but suppose you want to parse a tuple, say two values like (i32, Option<i32>). If I give the input 2 , would that be valid input? It has two spaces, one to separate the values and one to represent the None? It's odd.

Personally, I wouldn't get into that discussion right now. It should be a separate change. There's nothing stopping us from implementing FromStr for other types in the future.

@matthieu-m
Copy link

Personally, I wouldn't get into that discussion right now. It should be a separate change. There's nothing stopping us from implementing FromStr for other types in the future.

I think it's an important related discussion, though.

Whether FromStr is the trait which should be used for input is somewhat tied to how widely usable FromStr is.

Sure, a hello {name} tutorial or guess-the-number tutorial may only need a single string or number, and there FromStr works well enough.

The looming question, though, is whether input should also work for lists, sets, tuples, etc... out of the box, as that's essentially the next step in interactive applications: accepting not one, but two or more values.

Yes you can theoretically ask users to write their own FromStr implementation for their own times, but practically speaking... parsing by hand is a pain, so that's not going to leave a great impression. You're setting up a cliff in the learning experience.

(Asking users to input one element at a time, possibly in a loop, works too... but it's painful too, as now you need a way to "terminate" the loop)

And at this point, it does make me wonder whether FromStr is the appropriate trait for the task at hand.

A trait specialized for input could relatively easily be implemented for Vec, VecDeque, tuples, etc... with minimal fuss, because its usage would be more or less centered on input anyway. Perhaps it could even be possible for it to be automatically implemented for any type implementing FromStr.

In any case, it's a discussion which cannot really be tabled for later. If the input implementation uses FromStr, and then the libs-team refuses to have FromStr implemented for the std collections... input has been painted into a corner.

@tmccombs
Copy link

tmccombs commented May 5, 2025

But what would this hypothetical trait look like for collection types? Would it by separated by whitespace, a comma, a newline? What would if the type can contain the separator (for example String)? Or would it use some structured format similar to JSON?

@matthieu-m
Copy link

But what would this hypothetical trait look like for collection types? Would it by separated by whitespace, a comma, a newline? What would if the type can contain the separator (for example String)? Or would it use some structured format similar to JSON?

That's exactly the point of a distinct trait: it's up for grab.

Coming to a consensus on Display/FromStr for tuples or collections is a high bar, because they're used everywhere. 10 years in (or close to) they're still not defined, it's relatively unlikely they will ever be. And that's exactly because there's many reasonable representations, and it's not clear if there's ever going to be a consensus, given the variety of usecases.

On the other hand, for input, it's perfectly reasonable to make up the rules. There's one usecase (input), which defines the constraints (easy to type, preferably intuitive...), and that's it. In particular, there's no performance constraint here, so it'd be fine to backtrack and try again, etc... Perhaps comma is the preferred method, but in absence of comma, a split on whitespace is attempted?

(Not sure newline would ever be a good option, and while technically 0x1c..=0x1f exist for such purpose, they fail on ergonomics)

A dedicated trait gives flexibility, by relaxes many constraints that FromStr could face. It makes it possible for a further RFC to specify how to parse complex inputs.

@tmccombs
Copy link

tmccombs commented May 6, 2025

Sure you could come up with some format for handling Vec and tuples in this new trait. But there isn't an obvious choice of what it should be. And different applications may differ in how they want to handle it, or have it depend on the contained type.

@gg0074x
Copy link

gg0074x commented May 6, 2025

I support the idea of this input macro since its very useful for learning the basics of Rust or just simplify the input process, right now receiving input from the stdio its over-complicated specially for starters with the language.
Though i like it, i feel its not a complete approach, as i would include other stdio utility functions along with it.

Overall I find this approach really helpful for learners since a lot of exercises new programmers develop when learning a new language include basic stdio operations, printing is easy enough in Rust but as stated before, input can be more difficult to understand right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.