-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
base: master
Are you sure you want to change the base?
RFC: input macros #3799
Conversation
…nd its error handling
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:
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 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 |
Yes, that’s true, some have suggested it could be a function instead of a macro.
Rust not supporting function overloading shouldn’t mean we offer less ergonomic APIs. I’ve talked to many beginners who run into the same issue.
Currently, I don’t consider Rust to be strictly a systems programming language. In fact, the official slogan has changed to We’re not trying to hide 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 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 |
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. |
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
That's true, you make a good point, I'll polish the RFC a bit more Thanks |
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. |
There was a problem hiding this 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.
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, | ||
{ |
There was a problem hiding this comment.
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 implementFromStr
. However, it's possible that there isn't a canonical textual representation for the typeT
. - 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 implstd::fmt::{Display,Debug}
for their error type for various reasons.
There was a problem hiding this comment.
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 ofFromStr
. I suggested usingFromStr
, as mentioned in previous discussions, just to follow the library's style. We could useTryFrom
, but it would require more changes (I think).
For example,std::parse
usesFromStr
specifically for parsing. -
Maybe we could change the error type to
std::error::Error
, but it would be somewhat similar since it also implementsstd::fmt::Display
andstd::fmt::Debug
. It might be a bit limiting to useDisplay
andDebug
, 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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 implementFromStr
. However, it's possible that there isn't a canonical textual representation for the typeT
.
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 implstd::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.
PrintStyle::Continue => { | ||
// Use print! for no newline | ||
print!("{}", prompt_args); | ||
} | ||
PrintStyle::NewLine => { | ||
// Use println! for adding a newline | ||
println!("{}", prompt_args); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
let mut input = String::new(); | ||
let bytes_read = reader.read_line(&mut input).map_err(InputError::Io)?; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
## 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 (👋). |
There was a problem hiding this comment.
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?")
- https://github.com/DanielKeep/rust-scan-rules
- https://github.com/anowell/promptly
- https://github.com/jhg/scanf-rs
- https://github.com/wlentz/scan_fmt
- https://github.com/BartMassey/prompted
- https://github.com/oli-obk/rust-si
Notably, observe how the APIs between these ecosystem crates can be quite different, since the crates naturally tailor to different use cases (sometimes overlapping).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
@Phosphorus-M 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 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 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:
That would justify it and be really cool. Indeed 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? 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. |
Since Additionally, even if (edit) AFAICT the closest thing to docs: https://doc.rust-lang.org/stable/nightly-rustc/src/rustc_resolve/ident.rs.html#57-96 |
Alternatively, the following can be used: | ||
|
||
```rust | ||
let name: Option<Price> = try_input!("Please enter a price: ")?; | ||
``` |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
text/0000-input-macros.md
Outdated
println!("Please enter your name: "); | ||
let possible_name: Result<String, _> = input!(); // This could fail, for example | ||
// if the user closes the input | ||
// stream |
There was a problem hiding this comment.
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:
- It can be used in documentation and examples, where it is good to avoid distractions
- It is useful in real programs when the focus of the program is not "having a sensible CLI" but rather doing some complex work.
- 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.
There was a problem hiding this comment.
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
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, | ||
{ |
There was a problem hiding this comment.
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 implementFromStr
. However, it's possible that there isn't a canonical textual representation for the typeT
.
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 implstd::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.
I can't find a good justification for it being a macro. Yes, some language have this combined print!("Input your guess: ");
let guess = std::io::input()?; In fact, this "saves" us from having to standardize both an Note that in most cases, the users will need to be acquainted with One motivation for having That is, it's important to flush |
This example actually illustrates why combining them is useful, because this simple example is actually incorrect.
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. |
I maintain that getting input without a prompt is nonsensical. It's a use case that doesn't exist. |
* Can lead to unnecessary buffer allocations in Rust programs when developers | ||
don't realize that they could reuse a buffer instead. This could potentially | ||
be remedied by a new Clippy lint. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
This was my first thought as well. It's not a problem if |
No, it doesn't. The second part of the comment precisely explains that you need to have
I'll disagree, thrice.
My premise is that
First, I disagree with the fact that replacing Second, I disagree that not flushing Third, I therefore disagree with I would also note how easy it is to write a macro_rules! input {
() => { std::io::input() };
($($token:tt)+) => {{ print!($($token)+); std::io::input() }};
} Proving that |
@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
I note that there's an inherent |
Sorry for the delay, during the week I’m usually more focused on my work 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. let x: f64;
let y: f64;
input!("{x}, {y}"); You gave an example, and it could be similar if I simply implemented 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. 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, 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 |
Co-authored-by: Ian Jackson <[email protected]>
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. 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. |
Syntax is bike shedding. I can see a number of unresolved questions in the core functionality:
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? ... |
It looks useful, but perhaps it should start out as a third party crate. |
// 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 |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
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. 1. Yes,
Additionally, in future iterations we might consider the possibility of implementing Regarding types like 2. Yes, You can safely use If you want low-level control, 3. We should only trim the trailing Trimming the trailing 4. Structured input (e.g., In my opinion, 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 We could also consider evolving 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. |
@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:
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! |
Right, I agree with everything except the idea of implementing Personally, I wouldn't get into that discussion right now. It should be a separate change. There's nothing stopping us from implementing |
I think it's an important related discussion, though. Whether Sure, a hello The looming question, though, is whether Yes you can theoretically ask users to write their own (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 A trait specialized for In any case, it's a discussion which cannot really be tabled for later. If the |
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 |
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 |
Sure you could come up with some format for handling |
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. 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. |
This RFC proposes adding ergonomic
input!
,inputln!
and maytry_input!
macros to Rust for reading user input, inspired by Python’sinput()
.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:
stdin
? | forum internals.rust-lang.orgstd::io::input
simple input function. rust#75435This RFC is a compilation of comments and feedback provided on previous RFCs, especially RFC #3196 .
Rendered