From f8feb5610e963bd88544803804e6c86fbd7a5403 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Wed, 2 Apr 2025 02:25:44 -0300 Subject: [PATCH 01/20] add a introduction about input utilities --- text/0000-input-macro.md | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 text/0000-input-macro.md diff --git a/text/0000-input-macro.md b/text/0000-input-macro.md new file mode 100644 index 00000000000..51492c2290d --- /dev/null +++ b/text/0000-input-macro.md @@ -0,0 +1,97 @@ +- Feature Name: `input macros` +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +One paragraph explanation of the feature. + +# Motivation +[motivation]: #motivation + +Why are we doing this? What use cases does it support? What is the expected outcome? + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Introducing new named concepts. +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. +- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? +- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? + +# Prior art +[prior-art]: #prior-art + +Discuss prior art, both the good and the bad, in relation to this proposal. +A few examples of what this can include are: + +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? +- For community proposals: Is this done by some other community and what were their experiences with it? +- For other teams: What lessons can we learn from what other communities have done here? +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. + +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. +Please also take into consideration that rust sometimes intentionally diverges from common language features. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +# Future possibilities +[future-possibilities]: #future-possibilities + +Think about what the natural extension and evolution of your proposal would +be and how it would affect the language and project as a whole in a holistic +way. Try to use this section as a tool to more fully consider all possible +interactions with the project and language in your proposal. +Also consider how this all fits into the roadmap for the project +and of the relevant sub-team. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +If you have tried and cannot think of any future possibilities, +you may simply state that you cannot think of anything. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +The section merely provides additional information. From b9b307ff4b69c0649225516a9f5968b2498edd45 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Wed, 2 Apr 2025 21:39:21 -0300 Subject: [PATCH 02/20] add some examples for input macros to enhance user input handling --- text/0000-input-macro.md | 58 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/text/0000-input-macro.md b/text/0000-input-macro.md index 51492c2290d..e34476454ed 100644 --- a/text/0000-input-macro.md +++ b/text/0000-input-macro.md @@ -1,17 +1,66 @@ - Feature Name: `input macros` -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Start Date: 2025-04-02) - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary [summary]: #summary -One paragraph explanation of the feature. +This RFC propose the addition of macros and some functions that can be used to read input from the user in a more ergonomic way like the [Input built-in function in Python](https://peps.python.org/pep-3111/). + +With this initiative we can build a small interactive programs that reads input from standard input and writes output to standard output is well-established as a simple and fun way of learning and teaching Rust as a new programming language. + +```rust +println!("Please enter your name: "); +let possible_name: Result = input!(); // This could fail for example if the user closes the input stream + +// Besides we can show a message to the user +let possible_age: Result = 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 + +// --- Other way to use the macro --- + +struct Price { + currency: String, + amount: f64, +} + +impl FromStr for Price { + type Err = String; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split_whitespace().collect(); + if parts.len() != 2 { + return Err("String must have two parts".to_string()); + } + let currency = parts[0].to_string(); + let amount = parts[1].parse().unwrap(); + Ok(Price { currency, amount }) + } +} + +let price: Price = input!("Please introduce a price (format: '[currency] [amount]'): ")?; // This could fail for example if the input is reading from a pipe and we delete the file whose descriptor is being read meanwhile the program is running + +``` + +In this examples I show many ways to use the `input!` macro. + +In this macro we think that EOF is error case, so we return a `Result` with the error type being the error that caused the EOF. This is because is easily to handle the error for something new and we can mantain a similar behavior. + +However we can use besides: + +```rust +let name: Option = try_input!("Please introduce a price: ")?; +``` + +For example, that in this we can handle the error in a different way. +If we get a EOF we can return `None` and handle it in a different way but it's not exactly a error, it's a different case, EOF is valid but doesn't have a value, a way to represent this, that is why we use a `Option`. + +**DISCLAIMER**: The behavior of the `input!` to me is the most intuitive, but I think that the `try_input!` could be useful in some cases to be correct with the error handling. We can change the name of the macro `try_input!` or delete it if we think that is not necessary. It's just a idea, I'm open to suggestions. # Motivation [motivation]: #motivation -Why are we doing this? What use cases does it support? What is the expected outcome? +This kind of macros could be useful for beginners and reduce the barrier to entry for new Rustaceans. It would also make the language more friendly and help with the cognitive load of learning a new language. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -95,3 +144,6 @@ Note that having something written down in the future-possibilities section is not a reason to accept the current or a future RFC; such notes should be in the section on motivation or rationale in this or subsequent RFCs. The section merely provides additional information. + +Bullshit: +https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-prompt-dev \ No newline at end of file From 778af57540ac0a39bd37f9ac5ec937cc24db874a Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Wed, 2 Apr 2025 23:02:40 -0300 Subject: [PATCH 03/20] enhance input macro documentation to clarify behavior of try_input! and its error handling --- text/0000-input-macro.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-input-macro.md b/text/0000-input-macro.md index e34476454ed..5e8d4f4b18f 100644 --- a/text/0000-input-macro.md +++ b/text/0000-input-macro.md @@ -17,6 +17,9 @@ let possible_name: Result = input!(); // This could fail for example // Besides we can show a message to the user let possible_age: Result = 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 +// And yes this is a result so we can handle errors like this +let lastname = input!("Please enter your lastname: ").expect("The lastname is required"); // This could fail for example if the user enters a empty string + // --- Other way to use the macro --- struct Price { @@ -57,6 +60,8 @@ If we get a EOF we can return `None` and handle it in a different way but it's n **DISCLAIMER**: The behavior of the `input!` to me is the most intuitive, but I think that the `try_input!` could be useful in some cases to be correct with the error handling. We can change the name of the macro `try_input!` or delete it if we think that is not necessary. It's just a idea, I'm open to suggestions. +The behaviour of the `try_input!` is the same as the `input!` but the return type is `Result, InputError>`. The `InputError` is a enum that contains the error that could be returned by the `input!` macro. It was thinking thanks to the [commet of Josh Tripplet](https://github.com/rust-lang/rfcs/pull/3196#issuecomment-972915603) in [a previous RFC](https://github.com/rust-lang/rfcs/pull/3196). And to be honest yes, I think that is a good idea to have a way to handle the EOF, EOF is not exactly a error but it's a behaviour not too friendly usually because is a subtle distinction, is not exactly an edge case, but it's a less common scenario that people often overlook at first. + # Motivation [motivation]: #motivation From 2a9897b4feefc85fbef6fdf036f1729eec7edd4d Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Wed, 2 Apr 2025 23:56:12 -0300 Subject: [PATCH 04/20] refactor input prompt for price to simplify user instructions --- text/0000-input-macro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macro.md b/text/0000-input-macro.md index 5e8d4f4b18f..bcc5f54e363 100644 --- a/text/0000-input-macro.md +++ b/text/0000-input-macro.md @@ -41,7 +41,7 @@ impl FromStr for Price { } } -let price: Price = input!("Please introduce a price (format: '[currency] [amount]'): ")?; // This could fail for example if the input is reading from a pipe and we delete the file whose descriptor is being read meanwhile the program is running +let price: Price = input!("Please introduce a price: ")?; // This could fail for example if the input is reading from a pipe and we delete the file whose descriptor is being read meanwhile the program is running ``` From 7f09880a7910992a2f3da129ea19ae1f7e0ec9ed Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sun, 6 Apr 2025 04:27:23 -0300 Subject: [PATCH 05/20] add a lot of information into the RFC --- text/0000-input-macro.md | 191 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 3 deletions(-) diff --git a/text/0000-input-macro.md b/text/0000-input-macro.md index bcc5f54e363..2965ed2b5d2 100644 --- a/text/0000-input-macro.md +++ b/text/0000-input-macro.md @@ -67,12 +67,21 @@ The behaviour of the `try_input!` is the same as the `input!` but the return typ This kind of macros could be useful for beginners and reduce the barrier to entry for new Rustaceans. It would also make the language more friendly and help with the cognitive load of learning a new language. +The second chapter in the book talk about to make a guessing game, and in this chapter we can see how to read input from the user, but it is not too friendly and is not too easy to understand. It's really complex to explain to someone of high level what is a `Buffer` for give you a example, so in this case we can use the `input!` macro to make it easier. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: +Explaining the idea is not a rabbit hole, it's a very basic idea. +- We add a new macro `input!` that can be used to read input from the user in a more friendly way. This macro returns a `Result`. +- We add a new macro `try_input!` that can be used to read input from the user in a more friendly way but with a different behaviour, this macro return a `Result, InputError>`. +- In both cases we must to accept a type `T` where `T` is a type who implement `FromStr` trait, so we can convert the input to the type that we want. +- We must to specify the `InputError` type who must to be a enum that have three variants: + - `EOF` that is the error that we get when we reach the end of the input stream. + - `Parse(e)` that is the error that we get when we can't parse the input to the type that we want, `e` is the equivalent to a variable which type is `FromStr::Err`. + - `Io(e)` that is the error that we get when we have a IO error, `e` is the equivalent to a variable which type is `std::io::Error`. + -- Introducing new named concepts. - Explaining the feature largely in terms of examples. - Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. - If applicable, provide sample error messages, deprecation warnings, or migration guidance. @@ -84,6 +93,96 @@ For implementation-oriented RFCs (e.g. for compiler internals), this section sho # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +```rs +/// A macro that: +/// - reads **one line** from stdin (as `String` by default), +/// - returns `Err(InputError::Eof)` if EOF is encountered. +/// - returns `Err(InputError::Parse(e))` if the input cannot be parsed. +/// - returns `Err(InputError::Io(e))` if an IO error occurs. +/// +/// # Usage: +/// ```no_run +/// // No prompt +/// let text: String = input!().unwrap(); +/// +/// // With prompt +/// let name: String = input!("Enter your name: ").unwrap(); +/// +/// // Formatted prompt +/// let user = "Alice"; +/// let age: String = input!("Enter {}'s age: ", user).unwrap(); +/// ``` +#[macro_export] +macro_rules! input { + () => {{ + // If you want a different type, change here: + $crate::read_input_from(&mut ::std::io::stdin().lock(), None) + }}; + ($($arg:tt)*) => {{ + $crate::read_input_from( + &mut ::std::io::stdin().lock(), + Some(format_args!($($arg)*)) + ) + }}; +} + +/// A single function that: +/// 1. Optionally prints a prompt (and flushes). +/// 2. Reads one line from the provided `BufRead`. +/// 3. Returns `Err(InputError::Eof)` if EOF is reached. +/// 4. Parses into type `T`, returning `Err(InputError::Parse)` on failure. +/// 5. Returns `Err(InputError::Io)` on I/O failure. +pub fn read_input_from( + reader: &mut R, + prompt: Option>, +) -> Result> +where + R: BufRead, + T: FromStr, + T::Err: std::fmt::Display + std::fmt::Debug, +{ + if let Some(prompt_args) = prompt { + print!("{}", prompt_args); + // Always flush so the user sees the prompt immediately + io::stdout().flush().map_err(InputError::Io)?; + } + + let mut input = String::new(); + let bytes_read = reader.read_line(&mut input).map_err(InputError::Io)?; + + let trimmed = input.trim_end_matches(['\r', '\n'].as_ref()); + + // If 0, that's EOF — return Eof error + if trimmed == 0 { + return Err(InputError::Eof); + } + trimmed.parse::().map_err(InputError::Parse) +} + +/// A unified error type indicating either an I/O error, a parse error, or EOF. +#[derive(Debug, PartialEq)] +pub enum InputError { + /// An I/O error occurred (e.g., closed stdin). + Io(io::Error), + /// Failed to parse the input into the desired type. + Parse(E), + /// EOF encountered (read_line returned 0). + Eof, +} + +impl std::fmt::Display for InputError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + InputError::Io(e) => write!(f, "I/O error: {}", e), + InputError::Parse(e) => write!(f, "Parse error: {}", e), + InputError::Eof => write!(f, "EOF encountered"), + } + } +} + +impl std::error::Error for InputError {} +``` + This is the technical portion of the RFC. Explain the design in sufficient detail that: - Its interaction with other features is clear. @@ -95,11 +194,67 @@ The section should return to the examples given in the previous section, and exp # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +* Can lead to unnecessary buffer allocations in Rust programs when developers + don't realize that they could reuse a buffer instead. This could potentially + be remedied by a new Clippy lint. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives + +## Why should the macro trim newlines? + +We assume that the returned string will often be processed with `FromStr::from_str`, +for example it is likely that developers will attempt the following: + +```rs +let age: i32 = input!()?; +``` + +If `input()` didn't trim newlines the above would however always fail since +the `FromStr` implementations in the standard library don't expect a trailing +newline. Newline trimming is therefore included for better ergonomics, so that +programmers don't have to remember to add a `trim` or a `trim_end_matches` call +whenever they want to parse the returned string. In cases where newlines have to +be preserved the underlying `std::io::Stdin::read_line` can be used directly +instead. + +This is the default behavior in Python and C# for example, these cases trim +newlines by default. In Go, the `bufio.Reader.ReadString()` function +does not trim newlines, but the `bufio.Scanner` type does. The `bufio.Scanner` +type is the one that is used in the Go standard library for reading input +from the console. The `bufio.Scanner` type is a higher level abstraction. + +## Why should the function handle EOF? + +If the function performs newline trimming, it also has to return an error when +the input stream reaches EOF because otherwise users had no chance of detecting +EOF. Handling EOF allows for example a program that attempts to parse each line +as a number and prints their sum on EOF to be implemented as follows: + +```rs +fn main() -> Result<(), InputError> { + let mut sum = 0; + loop { + let result: Result = input!(); + match result { + Ok(mut prices) => sum += prices, + Err(InputError::Eof) => { + println!("Oh no, EOF!"); + break; + } + Err(e) => { + println!("Error: {:?}", e); // e could be a ParseFloatError + // or an IO error + return Err(e); + } + } + } + println!("{}", sum); + Ok(()) +} +``` + - Why is this design the best in the space of possible designs? - What other designs have been considered and what is the rationale for not choosing them? - What is the impact of not doing this? @@ -108,6 +263,18 @@ Why should we *not* do this? # Prior art [prior-art]: #prior-art +Python has [input()], Ruby has [gets], C# has `Console.ReadLine()` +... all of these return a string read from standard input. + +Some behaviors are different, for example: +- Python's `input()` returns a string, but if the input is EOF + it raises an `EOFError` exception. +- Ruby's `gets` returns `nil` if the input is EOF. +- C#'s `Console.ReadLine()` returns `null` if the input is EOF. +- Go's `bufio.Reader.ReadString()` returns an error if the input is EOF. + + + Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are: @@ -125,6 +292,12 @@ Please also take into consideration that rust sometimes intentionally diverges f # Unresolved questions [unresolved-questions]: #unresolved-questions +> What parts of the design do you expect to resolve through the RFC process +> before this gets merged? + +Should the function additionally be added to `std::prelude`, so that beginners +can use it without needing to import `std::io`? + - What parts of the design do you expect to resolve through the RFC process before this gets merged? - What parts of the design do you expect to resolve through the implementation of this feature before stabilization? - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? @@ -132,6 +305,18 @@ Please also take into consideration that rust sometimes intentionally diverges f # Future possibilities [future-possibilities]: #future-possibilities +Once this RFC is implemented: + +* The Chapter 2 of the Rust book could be simplified + to introduce mutability and borrowing in a more gentle manner. + +* Clippy might also introduce a lint to tell users to avoid unnecessary + allocations due to repeated `inputln()` calls and suggest + `std::io::Stdin::read_line` instead. + +With this addition Rust might lend itself more towards being the first +programming language for students. + Think about what the natural extension and evolution of your proposal would be and how it would affect the language and project as a whole in a holistic way. Try to use this section as a tool to more fully consider all possible From d132d986e28f89a000d8c8e40a2e94af0ce328d9 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Mon, 7 Apr 2025 01:18:34 -0300 Subject: [PATCH 06/20] move the texto follow the rules of 120 characters --- text/0000-input-macro.md | 127 ++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 34 deletions(-) diff --git a/text/0000-input-macro.md b/text/0000-input-macro.md index 2965ed2b5d2..ee55027587d 100644 --- a/text/0000-input-macro.md +++ b/text/0000-input-macro.md @@ -6,19 +6,32 @@ # Summary [summary]: #summary -This RFC propose the addition of macros and some functions that can be used to read input from the user in a more ergonomic way like the [Input built-in function in Python](https://peps.python.org/pep-3111/). +This RFC propose the addition of macros and some functions that can be used to +read input from the user in a more ergonomic way like the +[Input built-in function in Python](https://peps.python.org/pep-3111/). -With this initiative we can build a small interactive programs that reads input from standard input and writes output to standard output is well-established as a simple and fun way of learning and teaching Rust as a new programming language. +With this initiative we can build a small interactive programs that reads input +from standard input and writes output to standard output is well-established as +a simple and fun way of learning and teaching Rust as a new programming +language. ```rust println!("Please enter your name: "); -let possible_name: Result = input!(); // This could fail for example if the user closes the input stream +let possible_name: Result = input!(); // This could fail for example + // if the user closes the input + // stream // Besides we can show a message to the user -let possible_age: Result = 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 +let possible_age: Result = 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 -// And yes this is a result so we can handle errors like this -let lastname = input!("Please enter your lastname: ").expect("The lastname is required"); // This could fail for example if the user enters a empty string +// And yes, this is a result so we can handle errors like this +let lastname = input!("Please enter your lastname: ") + .expect("The lastname is required"); + // This could fail for example if the + // user enters a empty string // --- Other way to use the macro --- @@ -41,13 +54,18 @@ impl FromStr for Price { } } -let price: Price = input!("Please introduce a price: ")?; // This could fail for example if the input is reading from a pipe and we delete the file whose descriptor is being read meanwhile the program is running +let price: Price = input!("Please introduce a price: ")?; + // This could fail for example if the input is reading from a pipe and + // we delete the file whose descriptor is being read meanwhile the + // program is running ``` -In this examples I show many ways to use the `input!` macro. +In these examples I show many ways to use the `input!` macro. -In this macro we think that EOF is error case, so we return a `Result` with the error type being the error that caused the EOF. This is because is easily to handle the error for something new and we can mantain a similar behavior. +In this macro we think that EOF is error case, so we return a `Result` with the +error type being the error that caused the EOF. This is because is easily to +handle the error for something new and we can mantain a similar behavior. However we can use besides: @@ -56,39 +74,78 @@ let name: Option = try_input!("Please introduce a price: ")?; ``` For example, that in this we can handle the error in a different way. -If we get a EOF we can return `None` and handle it in a different way but it's not exactly a error, it's a different case, EOF is valid but doesn't have a value, a way to represent this, that is why we use a `Option`. - -**DISCLAIMER**: The behavior of the `input!` to me is the most intuitive, but I think that the `try_input!` could be useful in some cases to be correct with the error handling. We can change the name of the macro `try_input!` or delete it if we think that is not necessary. It's just a idea, I'm open to suggestions. - -The behaviour of the `try_input!` is the same as the `input!` but the return type is `Result, InputError>`. The `InputError` is a enum that contains the error that could be returned by the `input!` macro. It was thinking thanks to the [commet of Josh Tripplet](https://github.com/rust-lang/rfcs/pull/3196#issuecomment-972915603) in [a previous RFC](https://github.com/rust-lang/rfcs/pull/3196). And to be honest yes, I think that is a good idea to have a way to handle the EOF, EOF is not exactly a error but it's a behaviour not too friendly usually because is a subtle distinction, is not exactly an edge case, but it's a less common scenario that people often overlook at first. +If we get a EOF we can return `None` and handle it in a different way but it's +not exactly a error, it's a different case, EOF is valid but doesn't have a +value, a way to represent this, that is why we use a `Option`. + +**DISCLAIMER**: The behavior of the `input!` to me is the most intuitive, but I +think that the `try_input!` could be useful in some cases to be correct with the +error handling. We can change the name of the macro `try_input!` or delete it if +we think that is not necessary. It's just a idea, I'm open to suggestions. + +The behaviour of the `try_input!` is the same as the `input!` but the return +type is `Result, InputError>`. The `InputError` is a enum that +contains the error that could be returned by the `input!` macro. It was thinking +thanks to the +[commet of Josh Tripplet](https://github.com/rust-lang/rfcs/pull/3196#issuecomment-972915603) +in [a previous RFC](https://github.com/rust-lang/rfcs/pull/3196). And to be +honest yes, I think that is a good idea to have a way to handle the EOF, EOF is +not exactly a error but it's a behaviour not too friendly usually because is a +subtle distinction, is not exactly an edge case, but it's a less common scenario +that people often overlook at first. # Motivation [motivation]: #motivation -This kind of macros could be useful for beginners and reduce the barrier to entry for new Rustaceans. It would also make the language more friendly and help with the cognitive load of learning a new language. +This kind of macros could be useful for beginners and reduce the barrier to +entry for new Rustaceans. It would also make the language more friendly and help +with the cognitive load of learning a new language. -The second chapter in the book talk about to make a guessing game, and in this chapter we can see how to read input from the user, but it is not too friendly and is not too easy to understand. It's really complex to explain to someone of high level what is a `Buffer` for give you a example, so in this case we can use the `input!` macro to make it easier. +The second chapter in the book talk about to make a guessing game, and in this +chapter we can see how to read input from the user, but it is not too friendly +and is not too easy to understand. It's really complex to explain to someone of +high level what is a `Buffer` for give you a example, so in this case we can use +the `input!` macro to make it easier. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation Explaining the idea is not a rabbit hole, it's a very basic idea. -- We add a new macro `input!` that can be used to read input from the user in a more friendly way. This macro returns a `Result`. -- We add a new macro `try_input!` that can be used to read input from the user in a more friendly way but with a different behaviour, this macro return a `Result, InputError>`. -- In both cases we must to accept a type `T` where `T` is a type who implement `FromStr` trait, so we can convert the input to the type that we want. -- We must to specify the `InputError` type who must to be a enum that have three variants: - - `EOF` that is the error that we get when we reach the end of the input stream. - - `Parse(e)` that is the error that we get when we can't parse the input to the type that we want, `e` is the equivalent to a variable which type is `FromStr::Err`. - - `Io(e)` that is the error that we get when we have a IO error, `e` is the equivalent to a variable which type is `std::io::Error`. +- We add a new macro `input!` that can be used to read input from the user in a + more friendly way. This macro returns a `Result`. +- We add a new macro `try_input!` that can be used to read input from the user + in a more friendly way but with a different behaviour, this macro return a + `Result, InputError>`. +- In both cases we must to accept a type `T` where `T` is a type who implement + `FromStr` trait, so we can convert the input to the type that we want. +- We must to specify the `InputError` type who must to be a enum that have three + variants: + - `EOF` that is the error that we get when we reach the end of the input + stream. + - `Parse(e)` that is the error that we get when we can't parse the input to + the type that we want, `e` is the equivalent to a variable which type is + `FromStr::Err`. + - `Io(e)` that is the error that we get when we have a IO error, `e` is the + equivalent to a variable which type is `std::io::Error`. - Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. -- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? - -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +- Explaining how Rust programmers should *think* about the feature, and how it + should impact the way they use Rust. It should explain the impact as + concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or + migration guidance. +- If applicable, describe the differences between teaching this to existing Rust + programmers and new Rust programmers. +- Discuss how this impacts the ability to read, understand, and maintain Rust + code. Code is read and modified far more often than written; will the proposed + feature make code easier to maintain? + +For implementation-oriented RFCs (e.g. for compiler internals), this section +should focus on how compiler contributors should think about the change, and +give examples of its concrete impact. For policy RFCs, this section should +provide an example-driven introduction to the policy, and explain its impact in +concrete terms. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -115,7 +172,6 @@ For implementation-oriented RFCs (e.g. for compiler internals), this section sho #[macro_export] macro_rules! input { () => {{ - // If you want a different type, change here: $crate::read_input_from(&mut ::std::io::stdin().lock(), None) }}; ($($arg:tt)*) => {{ @@ -183,13 +239,15 @@ impl std::fmt::Display for InputError impl std::error::Error for InputError {} ``` -This is the technical portion of the RFC. Explain the design in sufficient detail that: +This is the technical portion of the RFC. Explain the design in sufficient +detail that: - Its interaction with other features is clear. - It is reasonably clear how the feature would be implemented. - Corner cases are dissected by example. -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. +The section should return to the examples given in the previous section, and +explain more fully how the detailed proposal makes those examples work. # Drawbacks [drawbacks]: #drawbacks @@ -204,8 +262,9 @@ The section should return to the examples given in the previous section, and exp ## Why should the macro trim newlines? -We assume that the returned string will often be processed with `FromStr::from_str`, -for example it is likely that developers will attempt the following: +We assume that the returned string will often be processed with +`FromStr::from_str`, for example it is likely that developers will attempt the +following: ```rs let age: i32 = input!()?; From ee4b572a43bbb0497e6ecd4c67d7d8fe67916f24 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Fri, 18 Apr 2025 22:22:00 -0300 Subject: [PATCH 07/20] add more details into the drawbacks --- ...00-input-macro.md => 0000-input-macros.md} | 188 +++++++++++------- 1 file changed, 119 insertions(+), 69 deletions(-) rename text/{0000-input-macro.md => 0000-input-macros.md} (70%) diff --git a/text/0000-input-macro.md b/text/0000-input-macros.md similarity index 70% rename from text/0000-input-macro.md rename to text/0000-input-macros.md index ee55027587d..2cb1e73da41 100644 --- a/text/0000-input-macro.md +++ b/text/0000-input-macros.md @@ -1,29 +1,29 @@ - Feature Name: `input macros` -- Start Date: 2025-04-02) +- Start Date: 2025-04-02 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary [summary]: #summary -This RFC propose the addition of macros and some functions that can be used to -read input from the user in a more ergonomic way like the +This RFC proposes the addition of macros and some functions that can be used to +read input from the user in a more ergonomic way, similar to the [Input built-in function in Python](https://peps.python.org/pep-3111/). -With this initiative we can build a small interactive programs that reads input -from standard input and writes output to standard output is well-established as -a simple and fun way of learning and teaching Rust as a new programming -language. +With this initiative, we can build a small interactive programs that reads input +from standard input and writes output to standard output. This is +well-established as a simple and fun way of learning and teaching Rust as a +new programming language. ```rust println!("Please enter your name: "); -let possible_name: Result = input!(); // This could fail for example +let possible_name: Result = input!(); // This could fail, for example // if the user closes the input // stream // Besides we can show a message to the user let possible_age: Result = input!("Please enter your age: "); - // This could fail for example if the + // This could fail, for example if the // user enters a string instead of a // number in the range of u8 @@ -33,7 +33,7 @@ let lastname = input!("Please enter your lastname: ") // This could fail for example if the // user enters a empty string -// --- Other way to use the macro --- +// --- Another way to use the macro --- struct Price { currency: String, @@ -56,12 +56,12 @@ impl FromStr for Price { let price: Price = input!("Please introduce a price: ")?; // This could fail for example if the input is reading from a pipe and - // we delete the file whose descriptor is being read meanwhile the + // we delete the file whose descriptor is being read while the // program is running ``` -In these examples I show many ways to use the `input!` macro. +In these examples, I show several ways to use the `input!` macro. In this macro we think that EOF is error case, so we return a `Result` with the error type being the error that caused the EOF. This is because is easily to @@ -152,7 +152,8 @@ concrete terms. ```rs /// A macro that: -/// - reads **one line** from stdin (as `String` by default), +/// - optionally prints a prompt (with `print!`). +/// - reads **one line** from stdin. /// - returns `Err(InputError::Eof)` if EOF is encountered. /// - returns `Err(InputError::Parse(e))` if the input cannot be parsed. /// - returns `Err(InputError::Io(e))` if an IO error occurs. @@ -172,12 +173,47 @@ concrete terms. #[macro_export] macro_rules! input { () => {{ - $crate::read_input_from(&mut ::std::io::stdin().lock(), None) + $crate::read_input_from( + &mut ::std::io::stdin().lock(), + None, + $crate::PrintStyle::Continue, + ) }}; ($($arg:tt)*) => {{ $crate::read_input_from( &mut ::std::io::stdin().lock(), - Some(format_args!($($arg)*)) + Some(format_args!($($arg)*)), + $crate::PrintStyle::Continue + ) + }}; +} + +/// A macro that: +/// - prints the prompt on its own line (with `println!`), +/// - then reads one line, +/// - returns `Err(InputError::Eof)` if EOF is encountered. +/// - returns `Err(InputError::Parse(e))` if the input cannot be parsed. +/// - returns `Err(InputError::Io(e))` if an IO error occurs. +/// - otherwise parses into `String`. +/// +/// # Usage: +/// ```no_run +/// let line: String = inputln!("What's your favorite color?").unwrap(); +/// ``` +#[macro_export] +macro_rules! inputln { + () => {{ + $crate::read_input_from( + &mut ::std::io::stdin().lock(), + None, + $crate::PrintStyle::NewLine + ) + }}; + ($($arg:tt)*) => {{ + $crate::read_input_from( + &mut ::std::io::stdin().lock(), + None, + $crate::PrintStyle::NewLine ) }}; } @@ -198,7 +234,16 @@ where T::Err: std::fmt::Display + std::fmt::Debug, { if let Some(prompt_args) = prompt { - print!("{}", prompt_args); + match print_style { + PrintStyle::Continue => { + // Use print! for no newline + print!("{}", prompt_args); + } + PrintStyle::NewLine => { + // Use println! for adding a newline + println!("{}", prompt_args); + } + } // Always flush so the user sees the prompt immediately io::stdout().flush().map_err(InputError::Io)?; } @@ -226,6 +271,15 @@ pub enum InputError { Eof, } +/// Defines how the prompt should be printed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PrintStyle { + /// Print the prompt without a trailing newline (uses `print!`). + Continue, + /// Print the prompt with a trailing newline (uses `println!`). + NewLine, +} + impl std::fmt::Display for InputError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -239,15 +293,17 @@ impl std::fmt::Display for InputError impl std::error::Error for InputError {} ``` -This is the technical portion of the RFC. Explain the design in sufficient -detail that: - -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. - -The section should return to the examples given in the previous section, and -explain more fully how the detailed proposal makes those examples work. +Another thing to consider is that the `input!` and `inputln!` macros just +delegate to the `read_input_from` function, which is the core of the +implementation. This function is generic over the reader type and the type +that we want to parse. Besides that, we have a `PrintStyle` enum that is used to +determine how to print the prompt. The `PrintStyle` enum has two variants: +- `PrintStyle::Continue` that is used to print the prompt without a trailing + newline. +- `PrintStyle::NewLine` that is used to print the prompt with a trailing + newline. +It allow us to use the same function for both macros and to have a more +ergonomic way to print the prompt. # Drawbacks [drawbacks]: #drawbacks @@ -314,10 +370,40 @@ fn main() -> Result<(), InputError> { } ``` -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? -- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? +## 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. + +I present this RFC as a Pre-RFC into a meetup of [Rust Argentina](https://rust-lang.ar/p/2025-april/) +and I receive a lot of positive feedback. I think that this RFC could be a good +idea to make the language more friendly and to help new Rustaceans to learn the +language. + +Many people in the meetup was new in the language it was the perfect public to +taste the idea. Mostly NodeJS, Python and Go developers. They were +enthusiastic about the idea and they think that it could be a good idea to +have a macro like this. And 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 minute when I present the idea was [this](https://youtu.be/CjZq93pzOkA?t=4080) +Sorry the presentation is in Spanish, but I add the time of the moment when I +present the idea. +And yes the guy with the black shirt is me. + +## Could this be done in a library or macro instead? + +Well yes, but I think that this is a good idea to have it in the standard +library. I think that this is a good idea to have it in the standard library +because it is a common use case and it is a good idea to have it in the +standard library to make the language more friendly. + +Another way that we could consider this is added like a pseudo oficial library +like `rand` which is not in the standard library but is recommended by the Rust +team and the oficial documentation as the book does it. # Prior art [prior-art]: #prior-art @@ -332,21 +418,10 @@ Some behaviors are different, for example: - C#'s `Console.ReadLine()` returns `null` if the input is EOF. - Go's `bufio.Reader.ReadString()` returns an error if the input is EOF. - - -Discuss prior art, both the good and the bad, in relation to this proposal. -A few examples of what this can include are: - -- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? -- For community proposals: Is this done by some other community and what were their experiences with it? -- For other teams: What lessons can we learn from what other communities have done here? -- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. - -This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. -If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. - -Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. -Please also take into consideration that rust sometimes intentionally diverges from common language features. +Maybe a thing to have in mind is that in JavaScript we have the `prompt` function +but this function in the case of the browser enable a little dialog when we +select cancel this dialog we receive a `null` value. +This is a [specification in JavaScript](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-prompt-dev). # Unresolved questions [unresolved-questions]: #unresolved-questions @@ -357,10 +432,6 @@ Please also take into consideration that rust sometimes intentionally diverges f Should the function additionally be added to `std::prelude`, so that beginners can use it without needing to import `std::io`? -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? - # Future possibilities [future-possibilities]: #future-possibilities @@ -375,24 +446,3 @@ Once this RFC is implemented: With this addition Rust might lend itself more towards being the first programming language for students. - -Think about what the natural extension and evolution of your proposal would -be and how it would affect the language and project as a whole in a holistic -way. Try to use this section as a tool to more fully consider all possible -interactions with the project and language in your proposal. -Also consider how this all fits into the roadmap for the project -and of the relevant sub-team. - -This is also a good place to "dump ideas", if they are out of scope for the -RFC you are writing but otherwise related. - -If you have tried and cannot think of any future possibilities, -you may simply state that you cannot think of anything. - -Note that having something written down in the future-possibilities section -is not a reason to accept the current or a future RFC; such notes should be -in the section on motivation or rationale in this or subsequent RFCs. -The section merely provides additional information. - -Bullshit: -https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-prompt-dev \ No newline at end of file From bf4e511af9337ac42537d9be7600d51a13f316fb Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 01:30:36 -0300 Subject: [PATCH 08/20] change the input macro documentation for clarity and consistency --- text/0000-input-macros.md | 133 ++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 70 deletions(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 2cb1e73da41..8334d6967c4 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -28,10 +28,10 @@ let possible_age: Result = input!("Please enter your age: "); // number in the range of u8 // And yes, this is a result so we can handle errors like this -let lastname = input!("Please enter your lastname: ") - .expect("The lastname is required"); - // This could fail for example if the - // user enters a empty string +let lastname: String = input!("Please enter your lastname: ") + .expect("The lastname is required"); + // This could fail for example if the + // user enters a empty string // --- Another way to use the macro --- @@ -54,58 +54,55 @@ impl FromStr for Price { } } -let price: Price = input!("Please introduce a price: ")?; +let price: Price = input!("Please enter a price: ")?; // This could fail for example if the input is reading from a pipe and // we delete the file whose descriptor is being read while the // program is running ``` -In these examples, I show several ways to use the `input!` macro. +The examples above demonstrate several ways to use the `input!` macro. -In this macro we think that EOF is error case, so we return a `Result` with the -error type being the error that caused the EOF. This is because is easily to -handle the error for something new and we can mantain a similar behavior. +In this macro, reaching EOF is consider an error case, so we return a `Result` +with an error type indicating the cause of the EOF. This approach makes error +handling straightforward and maintains consistent behavior. -However we can use besides: +Alternatively, the following can be used: ```rust -let name: Option = try_input!("Please introduce a price: ")?; +let name: Option = try_input!("Please enter a price: ")?; ``` -For example, that in this we can handle the error in a different way. -If we get a EOF we can return `None` and handle it in a different way but it's -not exactly a error, it's a different case, EOF is valid but doesn't have a -value, a way to represent this, that is why we use a `Option`. +In this case, EOF is not treated as an error, but as a valid case represented by +`None`. This allows handling EOF differently from other errors, since EOF +indicates the absence of a value rather than an error condition. This is why the +macro returns an `Option` type. -**DISCLAIMER**: The behavior of the `input!` to me is the most intuitive, but I -think that the `try_input!` could be useful in some cases to be correct with the -error handling. We can change the name of the macro `try_input!` or delete it if -we think that is not necessary. It's just a idea, I'm open to suggestions. +**Note**: The behavior of the `input!` is intended to be intuitive, but the +`try_input!` may be useful in cases where more nuanced error handling is +required. The name `try_input!` is provisional and open to change or removal +based on further discussion and feedback. -The behaviour of the `try_input!` is the same as the `input!` but the return +The behaviour of `try_input!` is similar to `input!` but its return type is `Result, InputError>`. The `InputError` is a enum that -contains the error that could be returned by the `input!` macro. It was thinking -thanks to the +contains the error that could be returned by the `input!` macro. This design was +inspired by [commet of Josh Tripplet](https://github.com/rust-lang/rfcs/pull/3196#issuecomment-972915603) -in [a previous RFC](https://github.com/rust-lang/rfcs/pull/3196). And to be -honest yes, I think that is a good idea to have a way to handle the EOF, EOF is -not exactly a error but it's a behaviour not too friendly usually because is a -subtle distinction, is not exactly an edge case, but it's a less common scenario -that people often overlook at first. +on [a previous RFC](https://github.com/rust-lang/rfcs/pull/3196). Handling EOF +as a distinct case (rather than an error) allows for more flexible error +handling, as EOF is not always an error but rather a less common scenario that +can be overlooked. # Motivation [motivation]: #motivation -This kind of macros could be useful for beginners and reduce the barrier to -entry for new Rustaceans. It would also make the language more friendly and help -with the cognitive load of learning a new language. +These macros could be especially useful for beginners, reducing the barrier to +entry for new Rustaceans. They would also make the language more approachable +and help lower the cognitive load when learning Rust. -The second chapter in the book talk about to make a guessing game, and in this -chapter we can see how to read input from the user, but it is not too friendly -and is not too easy to understand. It's really complex to explain to someone of -high level what is a `Buffer` for give you a example, so in this case we can use -the `input!` macro to make it easier. +For example, the second chapter of the Rust book introduces a guessing game and demonstrates how to read input from the user. The current approach is not very beginner-friendly and can be difficult to explain, especially concepts like +buffers. Using the `input!` macro would simplify this process and make it more +accessible. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -128,25 +125,6 @@ Explaining the idea is not a rabbit hole, it's a very basic idea. - `Io(e)` that is the error that we get when we have a IO error, `e` is the equivalent to a variable which type is `std::io::Error`. - -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it - should impact the way they use Rust. It should explain the impact as - concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or - migration guidance. -- If applicable, describe the differences between teaching this to existing Rust - programmers and new Rust programmers. -- Discuss how this impacts the ability to read, understand, and maintain Rust - code. Code is read and modified far more often than written; will the proposed - feature make code easier to maintain? - -For implementation-oriented RFCs (e.g. for compiler internals), this section -should focus on how compiler contributors should think about the change, and -give examples of its concrete impact. For policy RFCs, this section should -provide an example-driven introduction to the policy, and explain its impact in -concrete terms. - # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -334,8 +312,8 @@ whenever they want to parse the returned string. In cases where newlines have to be preserved the underlying `std::io::Stdin::read_line` can be used directly instead. -This is the default behavior in Python and C# for example, these cases trim -newlines by default. In Go, the `bufio.Reader.ReadString()` function +This is the default behavior in Python and C# for give you a example, these +cases trim newlines by default. In Go, the `bufio.Reader.ReadString()` function does not trim newlines, but the `bufio.Scanner` type does. The `bufio.Scanner` type is the one that is used in the Go standard library for reading input from the console. The `bufio.Scanner` type is a higher level abstraction. @@ -377,22 +355,18 @@ 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. -I present this RFC as a Pre-RFC into a meetup of [Rust Argentina](https://rust-lang.ar/p/2025-april/) -and I receive a lot of positive feedback. I think that this RFC could be a good -idea to make the language more friendly and to help new Rustaceans to learn the -language. +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. -Many people in the meetup was new in the language it was the perfect public to -taste the idea. Mostly NodeJS, Python and Go developers. They were -enthusiastic about the idea and they think that it could be a good idea to -have a macro like this. And 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. +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 minute when I present the idea was [this](https://youtu.be/CjZq93pzOkA?t=4080) -Sorry the presentation is in Spanish, but I add the time of the moment when I -present the idea. -And yes the guy with the black shirt is me. +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 (👋). ## Could this be done in a library or macro instead? @@ -440,6 +414,25 @@ Once this RFC is implemented: * The Chapter 2 of the Rust book could be simplified to introduce mutability and borrowing in a more gentle manner. + In the current version of the book, the code looks like this: + ```rust + let mut guess = String::new(); + + io::stdin() + .read_line(&mut guess) + .expect("Failed to read line"); + + let guess: u32 = guess.trim().parse().expect("Please type a number!"); + + println!("You guessed: {guess}"); + ``` + + With the new macros, it could be simplified to: + + ```rust + let guess: u8 = input!().expect("Please type a number!"); + ``` + * Clippy might also introduce a lint to tell users to avoid unnecessary allocations due to repeated `inputln()` calls and suggest `std::io::Stdin::read_line` instead. From ab3b87b3f74d8a282a4a3435ac3210b0facfa733 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 01:33:08 -0300 Subject: [PATCH 09/20] fix formatting in input macros documentation for consistency --- text/0000-input-macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 8334d6967c4..2c9ca74973f 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -382,7 +382,7 @@ team and the oficial documentation as the book does it. # Prior art [prior-art]: #prior-art -Python has [input()], Ruby has [gets], C# has `Console.ReadLine()` +Python has `input()`, Ruby has `gets`, C# has `Console.ReadLine()` ... all of these return a string read from standard input. Some behaviors are different, for example: From 6e6a666cdd171fdfca122947bf12d06ea32eed29 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 01:33:51 -0300 Subject: [PATCH 10/20] update start date for input macros RFC --- text/0000-input-macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 2c9ca74973f..e663e33c974 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -1,5 +1,5 @@ - Feature Name: `input macros` -- Start Date: 2025-04-02 +- Start Date: 2025-04-19 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) From 4de808ab422477f9faf182323fe57f3e919d6c56 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 01:34:52 -0300 Subject: [PATCH 11/20] rename feature name for consistency in input macros documentation --- text/0000-input-macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index e663e33c974..7cdefa1087a 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -1,4 +1,4 @@ -- Feature Name: `input macros` +- Feature Name: input_macros - Start Date: 2025-04-19 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) From ea8e32d3f11a8c7f4a50043a0b031f0ca91ad351 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 01:44:35 -0300 Subject: [PATCH 12/20] fix input handling to check bytes read instead of trimmed length --- text/0000-input-macros.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 7cdefa1087a..9b50894abe8 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -229,12 +229,12 @@ where let mut input = String::new(); let bytes_read = reader.read_line(&mut input).map_err(InputError::Io)?; - let trimmed = input.trim_end_matches(['\r', '\n'].as_ref()); - // If 0, that's EOF — return Eof error - if trimmed == 0 { + if bytes_read == 0 { return Err(InputError::Eof); } + + let trimmed = input.trim_end_matches(['\r', '\n'].as_ref()); trimmed.parse::().map_err(InputError::Parse) } From 4196483e22512d2b33d2537aa2dfc5b4aac14f50 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 02:02:18 -0300 Subject: [PATCH 13/20] update RFC PR reference for input macros documentation --- text/0000-input-macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 9b50894abe8..436e2d24bbd 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -1,6 +1,6 @@ - Feature Name: input_macros - Start Date: 2025-04-19 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3799](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From d07915208475aaf256d759b96c69c0f3c6a1b5ca Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sat, 19 Apr 2025 02:10:46 -0300 Subject: [PATCH 14/20] fix RFC PR reference in input macros documentation --- text/0000-input-macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 436e2d24bbd..4c1ba25f78f 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -1,6 +1,6 @@ - Feature Name: input_macros - Start Date: 2025-04-19 -- RFC PR: [rust-lang/rfcs#3799](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3799](https://github.com/rust-lang/rfcs/pull/3799) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From e5c04863b1a8bbd9d0ca8c5c414877efd6fb9ae9 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sun, 20 Apr 2025 10:29:06 -0300 Subject: [PATCH 15/20] add more context about the motivation --- text/0000-input-macros.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 4c1ba25f78f..31674d4f996 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -This RFC proposes the addition of macros and some functions that can be used to +This RFC proposes the addition of macros or some functions that can be used to read input from the user in a more ergonomic way, similar to the [Input built-in function in Python](https://peps.python.org/pep-3111/). @@ -100,10 +100,41 @@ These macros could be especially useful for beginners, reducing the barrier to entry for new Rustaceans. They would also make the language more approachable and help lower the cognitive load when learning Rust. -For example, the second chapter of the Rust book introduces a guessing game and demonstrates how to read input from the user. The current approach is not very beginner-friendly and can be difficult to explain, especially concepts like +For example, the second chapter of the Rust book introduces a guessing game and +demonstrates how to read input from the user. The current approach is not very +beginner-friendly and can be difficult to explain, especially concepts like buffers. Using the `input!` macro would simplify this process and make it more accessible. +This functionality is highly requested by the community, as evidenced we can +find a [PR in Rust](https://github.com/rust-lang/rust/pull/75435) with +many comments and a lot of discussion about this topic. We can also find +other [RFC](https://github.com/rust-lang/rfcs/pull/3183), +[another RFC](https://github.com/rust-lang/rfcs/pull/3196) and +many issues in the Rust repository that discuss this topic. +In addition, this is not a new idea, as we can find similar topics in +[internal.rust-lang discussions](https://internals.rust-lang.org/t/pre-rfc-input-macro/527). + +This RFC aims to provide a solution to this problem. + +Like many others, I would like to have a simple way to read input from the user +without having to deal with the complexities of the standard library. + +This RFC proposes allows us a more graceful introduction to Rust, not only a +utility function. It aims to make the language more approachable and +friendly for new users of the language. + +The idea behind of make a new macro provide of these discussion. +In many of these discussions, show examples of possible implementations, but +most of them use macros to implement this functionality. +The idea could be implemented as a function, too, actually [a old poll](https://strawpoll.com/zxds5jye6/results) +show that the majority want the feature, the implementation is not so important. +The implementation of this RFC is a macro, but it could be change to a function +if the community prefers that way or we could have both options. + +This poll was taken in [this comment](https://github.com/rust-lang/rfcs/pull/3183#issuecomment-979421461) +on a previous RFC. (Thanks to [undersquire](https://github.com/undersquire)) + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 63c574b3b3124c591ae81bc6ce19f84db2f5fc99 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Sun, 20 Apr 2025 10:46:13 -0300 Subject: [PATCH 16/20] refine language and clarity in input macros RFC documentation --- text/0000-input-macros.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 31674d4f996..2312472789c 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -106,8 +106,8 @@ beginner-friendly and can be difficult to explain, especially concepts like buffers. Using the `input!` macro would simplify this process and make it more accessible. -This functionality is highly requested by the community, as evidenced we can -find a [PR in Rust](https://github.com/rust-lang/rust/pull/75435) with +This functionality is highly requested by the community, we can find as +evidenced a [PR in Rust](https://github.com/rust-lang/rust/pull/75435) with many comments and a lot of discussion about this topic. We can also find other [RFC](https://github.com/rust-lang/rfcs/pull/3183), [another RFC](https://github.com/rust-lang/rfcs/pull/3196) and @@ -120,21 +120,33 @@ This RFC aims to provide a solution to this problem. Like many others, I would like to have a simple way to read input from the user without having to deal with the complexities of the standard library. -This RFC proposes allows us a more graceful introduction to Rust, not only a +This RFC proposes to allow us a more graceful introduction to Rust, not only a utility function. It aims to make the language more approachable and friendly for new users of the language. -The idea behind of make a new macro provide of these discussion. -In many of these discussions, show examples of possible implementations, but +The idea behind creating a new macro comes from these discussions. +In many of these discussions, examples are shown of possible implementations, but most of them use macros to implement this functionality. -The idea could be implemented as a function, too, actually [a old poll](https://strawpoll.com/zxds5jye6/results) -show that the majority want the feature, the implementation is not so important. -The implementation of this RFC is a macro, but it could be change to a function +The idea could be implemented as a function, too, actually [an old poll](https://strawpoll.com/zxds5jye6/results) +shows that the majority wants the feature, the specific implementation is less +important. +The implementation of this RFC is a macro, but it could be changed to a function if the community prefers that way or we could have both options. -This poll was taken in [this comment](https://github.com/rust-lang/rfcs/pull/3183#issuecomment-979421461) +This poll was mentioned in [this comment](https://github.com/rust-lang/rfcs/pull/3183#issuecomment-979421461) on a previous RFC. (Thanks to [undersquire](https://github.com/undersquire)) +Besides, the idea of this implementation is solve too the problem of parsing +some types for example in other languages we have functions like `nextInt()` in +Java which is a function that reads an integer from the input stream and returns +it. We don't have this in Rust, we have to use the `read_line` function and then +trim the string and parse it into the type that we want. +And this is a very common use case, we sometime read a string from the input +but we want to parse it into a number or a struct. + +If we repeat this process many times in our code, we have a lot of boilerplate +code that we can avoid with this macro. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 70d1ee13384aaef1fbfb8103054ebf67eac876d2 Mon Sep 17 00:00:00 2001 From: Phosphorus Moscu Date: Sat, 26 Apr 2025 12:51:47 -0300 Subject: [PATCH 17/20] change a `println!` to a `print!` Co-authored-by: Ian Jackson --- text/0000-input-macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 2312472789c..90962083665 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -16,7 +16,7 @@ well-established as a simple and fun way of learning and teaching Rust as a new programming language. ```rust -println!("Please enter your name: "); +print!("Please enter your name: "); let possible_name: Result = input!(); // This could fail, for example // if the user closes the input // stream From 0c7a542ac3775dce01ddf3a2528fecbe0caab87f Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Fri, 11 Jul 2025 01:27:40 -0300 Subject: [PATCH 18/20] add Java's EOF behavior to input macros documentation --- text/0000-input-macros.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 90962083665..7dc8ba6b7fa 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -434,6 +434,7 @@ Some behaviors are different, for example: - Ruby's `gets` returns `nil` if the input is EOF. - C#'s `Console.ReadLine()` returns `null` if the input is EOF. - Go's `bufio.Reader.ReadString()` returns an error if the input is EOF. +- Java's `readln` returns an `null` if the input is EOF. Maybe a thing to have in mind is that in JavaScript we have the `prompt` function but this function in the case of the browser enable a little dialog when we @@ -474,6 +475,7 @@ Once this RFC is implemented: ```rust let guess: u8 = input!().expect("Please type a number!"); + println!("You guessed: {guess}"); ``` * Clippy might also introduce a lint to tell users to avoid unnecessary From 4b6918ff10e10e3af4f613aea4257c2be4f349e1 Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Wed, 13 Aug 2025 23:10:25 -0300 Subject: [PATCH 19/20] add missing imports and enhance error handling in input macro --- text/0000-input-macros.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 7dc8ba6b7fa..64bd02e9410 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -172,6 +172,8 @@ Explaining the idea is not a rabbit hole, it's a very basic idea. [reference-level-explanation]: #reference-level-explanation ```rs +use std::{fmt::Arguments, io::{self, BufRead, Write}, str::FromStr}; + /// A macro that: /// - optionally prints a prompt (with `print!`). /// - reads **one line** from stdin. @@ -248,6 +250,7 @@ macro_rules! inputln { pub fn read_input_from( reader: &mut R, prompt: Option>, + print_style: PrintStyle, ) -> Result> where R: BufRead, @@ -282,7 +285,7 @@ where } /// A unified error type indicating either an I/O error, a parse error, or EOF. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub enum InputError { /// An I/O error occurred (e.g., closed stdin). Io(io::Error), From 1463d6469a4a50f5278aeab17f3d2f6ef199fc3f Mon Sep 17 00:00:00 2001 From: Phosphorus-M Date: Mon, 1 Sep 2025 15:37:18 -0300 Subject: [PATCH 20/20] add installation instructions for input-lib in input macros documentation --- text/0000-input-macros.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-input-macros.md b/text/0000-input-macros.md index 64bd02e9410..0c3cd2dc1d2 100644 --- a/text/0000-input-macros.md +++ b/text/0000-input-macros.md @@ -329,6 +329,12 @@ determine how to print the prompt. The `PrintStyle` enum has two variants: It allow us to use the same function for both macros and to have a more ergonomic way to print the prompt. +You can find the implementation in this [repository](https://github.com/Phosphorus-M/input-lib) or you can install it from crates.io with: + +```sh +cargo install input-lib +``` + # Drawbacks [drawbacks]: #drawbacks