Skip to content
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

RFC: Add std::io::inputln() #3196

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

Conversation

not-my-profile
Copy link

@not-my-profile not-my-profile commented Nov 16, 2021

I think the best way to drive the addition of this function forward is to have a dedicated RFC for it.

Rendered

Previous discussions:

@not-my-profile not-my-profile changed the title Add RFC for std::inputln() RFC: Add std::inputln() Nov 16, 2021
@undersquire
Copy link

I think that this might be a better idea. In my RFC I proposed a more advanced approach which ultimately evolved to have formatting, however that is a lot more complex to implement and might be better to leave to crates. I think the standard library would benefit more from a simpler implementation like the one suggested here.

@not-my-profile not-my-profile changed the title RFC: Add std::inputln() RFC: Add std::io::inputln() Nov 16, 2021
Since it's a function it can be added to std::prelude.
@clarfonthey
Copy link
Contributor

I'm personally all for this method, but I think that the method should be called read_line because it matches the corresponding Stdin method. All of the related FS and IO methods that have standalone equivalents do the same, and the change of name here implies that there's something different happening. This also saves inputln as a name for later if we want a more advanced macro approach.

@undersquire
Copy link

I'm personally all for this method, but I think that the method should be called read_line because it matches the corresponding Stdin method. All of the related FS and IO methods that have standalone equivalents do the same, and the change of name here implies that there's something different happening. This also saves inputln as a name for later if we want a more advanced macro approach.

Since there is already a read_line function, wouldn't it be confusing or even cause conflicts to name this function the same way? Or are you implying that this version replaces the existing read_line implementation?

@not-my-profile
Copy link
Author

not-my-profile commented Nov 16, 2021

std::io::read_line and std::io::Stdin::read_line can both exist perfectly fine without conflicting. Nobody is suggesting to replace any existing function: Rust guarantees backwards compatibility!

@clarfonthey
Copy link
Contributor

Since there is already a read_line function, wouldn't it be confusing or even cause conflicts to name this function the same way? Or are you implying that this version replaces the existing read_line implementation?

I think a specific example might help: https://doc.rust-lang.org/std/fs/fn.read_to_string.html

One read_to_string is a method on File which accepts a buffer, whereas the other is a standalone function which takes in a filename and allocates a new string.

@lebensterben
Copy link

Note that print!() and println!() are "specialised" variant of write!() and writeln!(), where all of them are based on io::Write but the first two specifically writes to stdout.

So analogously, we should have something that provides a more convenient interface to io::Read first, then to provide a more specific one such as inputln().

Introducing inputln() without something like read() or readln() just feels weird logically.

@not-my-profile
Copy link
Author

not-my-profile commented Nov 16, 2021

Thanks Clar, that is a really good observation! I however see a different consistency axis: for any function named read I expect it to take an argument to specify from where you want to read:

  • the std::io::Read::read* methods read from the passed &mut self
  • std::io::read_to_string reads from the passed reader
  • std::fs::read_to_string reads from the passed file path

So having a std::io::read_line function that does not let you choose where to read from just feels wrong to me.

@undersquire
Copy link

undersquire commented Nov 16, 2021

I am not sure this is a big deal, but I tried using your function in a test project and noticed that it doesn't work with parsing to other values. For example:

let age: i32 = io::inputln()?.parse().expect("Invalid age!");

This will always fail here, because when it reads it uses the std::io::stdin().readline function, which also includes the \n (newline) in the resulting string. This isn't a huge deal if you expect the user to manually remove the newline, however I think it might be better to do that for them:

pub fn read_line() -> Result<String, std::io::Error> {
    let mut line = String::new();

    std::io::stdin().read_line(&mut line)?;

    Ok(line.trim_end_matches('\n').to_string())
}

I might be wrong, but I see no reason to include the newline character at the end.

EDIT: Why does std::io::stdin().read_line even include the newline? I feel like that should be changed because you really don't need that newline it reads.

@not-my-profile
Copy link
Author

Good observation @undersquire! I however don't think that this is necessary. You can just do:

let age: i32 = io::inputln()?.trim().parse().expect("Invalid age!");

which also has the benefit that it does not fail when the user accidentally enters other whitespace before or after their input.

Why even does std::io::stdin().read_line include the newline? I feel like that should be changed because you really don't need that newline it reads.

Please stop talking about breaking backwards compatability 😅

@Diggsey
Copy link
Contributor

Diggsey commented Nov 16, 2021

read_line includes the newline because it's intended to be a lower-level building block, and it may be important for the caller to know how the line was terminated.

Given that the entire purpose of inputln would be as a convenience function, then it shouldn't include the newline.

This is the same reason that read_line is passed a buffer to read into, whereas inputln should just allocate and return a stirng.

@not-my-profile
Copy link
Author

That's a very good point! I updated the RFC to include the same newline trimming as std::io::BufRead::lines has.

@m-ou-se
Copy link
Member

m-ou-se commented Nov 17, 2021

What does this function do when it hits EOF or reads a non-newline-terminated line?

Python's input() throws an exception on a zero read() with no input, but running inputln()? in a loop will run forever when hitting the end of a file that's redirected to stdin:

fn main() -> std::io::Result<()> {
    loop {
        dbg!(inputln()?);
    }
}
$ cargo r < /dev/null
[src/main.rs:16] inputln()? = ""
[src/main.rs:16] inputln()? = ""
[src/main.rs:16] inputln()? = ""
[src/main.rs:16] inputln()? = ""
[...never stops...]

@Diggsey
Copy link
Contributor

Diggsey commented Nov 17, 2021

I would expect inputln to return an error if used again after EOF was already hit. eg.
So:

<EOF> => Ok(""), Err
foo<EOF> => Ok("foo"), Err
foo\n<EOF> => Ok("foo"), Ok(""), Err

@undersquire
Copy link

undersquire commented Nov 17, 2021

What does this function do when it hits EOF or reads a non-newline-terminated line?

The proposed inputln function simply uses std::io::stdin().read_line under the hood, so however std::io::stdin().read_line handles EOF is how inputln will also handle EOF. If it doesn't then maybe it needs to be added to the proposed function.

@yaahc yaahc added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Nov 17, 2021
@undersquire
Copy link

@not-my-profile Is the function being renamed to std::io::read_line or are you just keeping it as std::io::inputln?

@not-my-profile
Copy link
Author

not-my-profile commented Nov 18, 2021

Thanks @m-ou-se, you're of course right, when the function performs newline trimming it also has to convert zero reads to UnexpectedEof errors, or otherwise users have no chance of detecting EOF. I added EOF handling to the RFC (along with rationales for both newline trimming and EOF handling).

Is the function being renamed to std::io::read_line or are you just keeping it as std::io::inputln?

If the function would just allocate a string, call std::io::Stdin::read_line and return the string/error unchanged, then I would agree that it should also be called read_line. In particular because as @clarfonthey remarked there is precedent for that with std::fs::read_to_string and std::io::read_to_string both just allocating a new string, calling Read::read_to_string and returning the resulting string/error.

But now that the function is additionally trimming newlines and handling EOF, I am thinking that a distinct function name might be a better choice, so that API users aren't mislead into thinking that the function returns the string/error unchanged, as is the case with the read_to_string functions.

Do we want two functions in the same standard library module with the same name but subtly different behavior? Is there precedent for that? Is that desirable?

@joshtriplett
Copy link
Member

Rather than returning an error on EOF, I personally would expect this to be an expected possibility to handle: Result<Option<String>>, where Ok(None) indicates an EOF with no available data.

@jsimonss
Copy link

The RFC notes that contrary to println! and friends, that are macros, inputln is a function and that printing a prompt with print doesn't flush. Couldn't both be addressed by instead of making inputln a function it would be a macro.

inputln!() would function like the proposed function, inputln!(...) would do a print!(...), flush, and read line.

This would add to the namespace but the RFC seems to propose that the function is available in prelude in any case.

@BartMassey
Copy link

I've started pointing my students at prompted, my crate for doing this stuff. It's not perfect, but is convenient. I think the bikeshedding and conflicts over interface and function will prevent anything useful from getting into std, although something simple like prompted probably should. The "Guessing Game" introductory example in TRPL is embarrassing.

@Phosphorus-M
Copy link
Contributor

I've started pointing my students at prompted, my crate for doing this stuff. It's not perfect, but is convenient. I think the bikeshedding and conflicts over interface and function will prevent anything useful from getting into std, although something simple like prompted probably should. The "Guessing Game" introductory example in TRPL is embarrassing.

I agree with you, in my case, my community (Rust Lang Es) do divulgation about the language, and it's one of the most frequent questions in new developers.

@bazylhorsey
Copy link

bazylhorsey commented Nov 16, 2023

Great implementation using macros @BartMassey
I've just released my repo prompt-rust which casts the prompted input to a primitive type using the logic above. https://crates.io/crates/prompt-rust

Pieces I like of this implementation is leveraging generics, implementing everything based on FromStr trait to handle nearly any common cast, generic error handling (yours returns a string panic), and abstracting the allocation of the String itself (unless the cast is to a String of course).

I think macros are probably a less opinionated approach and easy to understand. Still requires a match statement to cast, but you have to do that in every dynamic language prompt ( x = int(input("input:")) )
I'm an intermediate/learning Rust hobbyist, so my opinion isn't definitive,but this could certainly be the more correct approach in the std.

An outsider with a lot of experience building standard library functions reviewing different methods presented here would be 👩‍🍳 💋

I think a main argument point should be around the guessing game example. "Rust is powerful, yet high-level" is a branding the community tries to run with... but the guessing game does not do a very good job of getting the point across. Failing this negatively affects adoption; and thus, reduces awareness. Rust has done great despite this, but I know many JavaScript and python devs who look at my rust code and are like, "why would you do this to yourself." They don't necessarily understand the full potential or picture behind the reasons to use Rust.
To my point, the community should come together for this to help people see, "wow, this looks very similar to my JavaScript implementation" while achieving the same benchmarks.

Other points on why this should be accepted:

  • general to many programming problems
  • low code addition
  • simplified documentation
  • implementation could easily cover nearly every common use-case for using said functionality over coding by hand with near-zero loss in performance/build-optimization.

@BartMassey
Copy link

Problems with prompt-rust's prompt() for new users and convenience:

  • An I/O error on user input normally does not want to be handled. Panicking the program is a more convenient response, since there's no obvious way to recover. This is also much friendlier to new Rustaceans.

  • The problem of parse errors in type conversions is a thing. It is made worse by Rust's frequent inability to infer types "backward" to derive an appropriate result type. Play around with this example a little to see the thing:

use std::str::FromStr;                                                          
                                                                                
fn parsery<T: FromStr>(s: &str) -> Result<T, <T as FromStr>::Err> {             
    T::from_str(s)                                                              
}                                                                               
                                                                                
fn main() {                                                                     
    let s = parsery("4.5").unwrap();                                            
    let t = s + 7.0f32;                                                         
    println!("{}", t);                                                          
}                                                                               
  • Having format capabilities would be really convenient.

These are the kinds of issues that made me have my input!() return String: just separation of concerns and least surprise stuff.

All of that said, I think these kinds of differences are a part of what make people reluctant to stick anything in std. It's complicated.

@BartMassey
Copy link

BTW, any friendly line reader should maybe use GNU readline or similar for line editing. Or maybe not.

@bazylhorsey
Copy link

bazylhorsey commented Nov 16, 2023

Excellent first point, but a major goal it seems in rust is make advanced things simple. An advanced programmer could find reasons to use the Error result and do something with it (like maybe set a new variable or provide a match. I think impl Display and Debug were my ways of providing user feedback. It is true beginners would not know how to use errors returned, but that is not gonna convince managers of rust to implement alone.

Moving on, wow that's the most wtf thing I've seen in rust, truly undefined behavior (I started seriously learning post-stable). I wonder what could cause this? Do they use the same address of bytes? Would setting s to f32 at instantiating assist the compiler in allocating smarter? I'll for sure play around.

Finally, like most open source things if we find a consensus ourselves and can get a couple more people on board, people will eventually fall inline. The people above talking about scanner is a stretch too far in use-cases and further complication. If anything it's a later iteration. This is not just my opinion, but the consensus here. @BartMassey I would be willing to split work and collaborate with you, so far as committing to maintenance (should be minimal). A couple people willing to lead it also is gonna make this more appetizing to people who would give the green light! 😃

@bazylhorsey
Copy link

bazylhorsey commented Nov 16, 2023

Linereader should be the responsibility of the IO libraries we (as well as other depending libs/internals) use. I haven't seen any compatability issues ever for IO.

@tmccombs
Copy link

An I/O error on user input normally does not want to be handled

In a demo/tutorial/exploration program, sure.

In a real program, you may want to "handle" it by printing a more user friendly error message before aborting.

@bazylhorsey
Copy link

bazylhorsey commented Nov 18, 2023

So let's start with a finished library for integration to base off of, what I'm reading is:

  • handle true generic errors
  • general to use cases outside beginner-level code
  • includes interface as a macro, functions they rely on can be made public or kept private at later stage, for now both will be valid interfaces.
  • stdin as bytes, no scan consideration, will move to another rfc someone can take over.
  • with consideration to issues parsing in a single step to a primitive, we'll stick to returning a str. While rust casting seems to be a cool feature we could provide, javascript and python both have functionality that requires a cast, so this won't seem like a huge loss.

I'm gonna set a reminder for myself to come back in 2 weeks where I'd like to get some more consensus points before sending this rfc more fleshed out as a library. I will go ahead and put in any work required to get things working like we agree on this board. Please pr to bazylhorsey/io-cast for direct changes or recommend them here. If we diverge in thoughts I'll make a couple different branches with different ways to solve the problem which the rust leads can take from there. I have a good feeling organizing things this way with a few people who keep this conversation going will get this to the finish line. Cheers! 😃

@BartMassey
Copy link

Thanks much for summarizing some of the issues!

  • "handle true generic errors" — Strongly disagree, for the reasons I have expressed. I/O errors reading standard input or writing standard output are truly rare: I literally can't remember one in the last few years of continuous programming, and anything that needs this level of reliability (without itself just panicking) likely needs a whole bunch more. We can pick a panic message that accurately indicates the source of the error — not that anyone will ever see it.

  • "general to use cases outside beginner-level code" — If this is prototyping, casual use etc, sure. Again, for things that want to handle user input in a production-quality CLI, I think the existing stdio (such as it is) is a better choice.

  • "stdin as bytes" — Not sure what is meant here. If the intent is to handle non-UTF8 inputs, probably not? Something that returns a String really can't deal with this anyhow. Again, just panic with an appropriate message.

  • "we'll stick to returning a str" — A String, but yes.

Issues to be added:

  • A big part of the prompted crate is handling flushing of non-newline-terminated prompts before reading from input. This papers over the decision by the Rust stdio folks to not follow the C stdio tradition of flushing stdout before every read from stdin. Once prompted had this, it also provided flushed non-newline-terminated output as a separate macro, along with a couple of examples of why it is useful in its own right.

  • Line endings are actually sort of a problem? Windows still uses CRLF as a line terminator, while other OSes use LF. Should the behavior with respect to CRLF at end of line be different on Windows than other boxes? (I am not aware of anything in Rust std that reliably removes line endings in a platform-dependent way, but I may have missed something.)

@bazylhorsey
Copy link

bazylhorsey commented Nov 19, 2023

Why should we return a String if we already have allocated what we need. I'd argue str is more powerful and better performing. Moving the data from the heap to the stack seems sane. If the user wants to mutate again they can use the as_string function right? Most use cases I image they're only ever going to be reallocating if they want the string which they then either scrub or change values. My understanding is str is an array, and String is a vector. In our case once you grab the line you know they exact amount of bytes you need so the str should be returned and owned by the variable that called this function.

For the panic stuff, maybe someone else can weigh in here. In my implementation, I'm still printing the error the same way you are, but at the same time presenting the option of using the generic error so it can be caught in a match. IMO there's no downside to this as you still get a message in debug which is for beginner purposes fulfills your points while being a more full solution.

@BartMassey
Copy link

Why should we return a String if we already have allocated what we need?

Allocated where? I don't understand the proposed implementation: short of passing a buffer into the macro I see no place to put the read data except on the heap. What lifetime do you propose that the returned &str have? An example would be really helpful if I'm missing something.

IMO there's no downside to this as you still get a message in debug which is for beginner purposes fulfills your points while being a more full solution.

I strongly prefer not having to unwrap() every input!(), which is certainly how I and my students would use it if it returned a Result. What sort of special handling would you want to do on an input!() that returned Err? What's a good practical example of the usefulness of this?

Genuinely sorry to be so argumentative. I'll bow out now and let you and others discuss.

@bazylhorsey
Copy link

bazylhorsey commented Nov 19, 2023

No no, this is great stuff.
Argument is the name of the game.

Not having to unwrap is something I didn't think of, and while valid, look at other things in std where unwrap is pretty commonplace. In general I think panic should be kept for things outside rusts implementation when you know the behavior is isolated. When I first was learning rust I was like, why do I have to unwrap everything, now it makes sense and I see the power of handling errors in a multitude of different ways. If someone wanted to then make a library that abstracts it with a panic that sounds fine. As for std we should not make assumptions around beginner-based use. It's still multitudes simpler than doing io yourself with its current implementation.

I'm still not a rust god, but in my head I have it compartmentalized as: if I'm mutating a string i use String, otherwise I use str. In our case obviously at some point we need to construct an unknown length of bytes into a vector so String is obviously a piece, but for the return we should pass it to str because it's more low-level, implements FromStr is straightforward, and the user can always recast it to a String of the value changes. Strings are heavier more complex objects the programmer should know when they need to allocate a mutable space in the heap. My 2 cents.

@tmccombs
Copy link

I/O errors reading standard input or writing standard output are truly rare

not if you have any kind of redirection or piping going on. For example if stdin is a pipe, and the process on the other end terminates, you will get an I/O error (if your process isn't killed by SIGPIPE).

And if you want to avoid Result altogether, that means you also need to panic if stdin is closed/reached end of stream. Which isn't exactly rare.

@programmerjake
Copy link
Member

For example if stdin is a pipe, and the process on the other end terminates, you will get an I/O error (if your process isn't killed by SIGPIPE).

actually, afaict you just get EOF when reading from a pipe where the write end was closed (after reading any cached data). you get SIGPIPE and/or EPIPE when writing where the read end was closed.

@bazylhorsey
Copy link

@programmerjake considering this do you think a result should be returned from the function?

@programmerjake
Copy link
Member

programmerjake commented Nov 21, 2023

@programmerjake considering this do you think a result should be returned from the function?

yes, because EOF can be considered an error and is quite common. after all, you want to be able to distinguish between reading an infinite sequence of empty lines and reaching the end of a file.

@bazylhorsey
Copy link

bazylhorsey commented Nov 21, 2023

@programmerjake any thoughts on how what type we should return or parse, and where we should draw the line?

@programmerjake
Copy link
Member

@programmerjake any thoughts on how what type we should return or parse, and where we should draw the line?

pub fn inputln() -> io::Result<String> {...}

Python's input raises EOFError when hitting EOF, why not match that and return an UnexpectedEof error?

usage:

let foo: i32 = inputln()?.parse();

alternatively, have it return io::Result<Option<String>> returning None when it hits EOF.

usage:

loop {
    let Some(line) = inputln()? else {
        break;
    };
    let v: i32 = line.parse()?;
    println!("{v} * 2 == {}", v * 2);
}

@bazylhorsey
Copy link

bazylhorsey commented Nov 21, 2023

@programmerjake
On my notes above, is String really the best return? FromStr is implemented off of str. And the space is already allocated as a final for this functionality. What is the reasoning for returning String? Shouldn't there be an ownership handoff for better performance?

Also you think this should also be a macro?

Do you have a preference or intuition on if Option String or expected String in your example of if it's more likely to be considered acceptable to rusts team?

Just collecting your thoughts while I have your attention :)

@bazylhorsey
Copy link

Also everyone don't forget to cast your vote from this old RFC

https://strawpoll.com/zxds5jye6

This will be a good community study for a considered proposal

@tmccombs
Copy link

Shouldn't there be an ownership handoff for better performance?

What would own the underlying data?

Or perhaps written another way, what would be the lifetime of the returned str?

The possibilities I see for that are:

  1. Accept a &'a mut String as an argument and return &'a str, so you can store it an object the caller allocated. This complicates the API thoguh, and makes it harder to use.
  2. Accept a &'a mut [u8], write to that and return &'a str. This has the same problem as 1, but is more flexible in where you store the data. But it also has the additional problem that the buffer might not be big enough to store the result.
  3. Create a separate wrapper object that you would call inputln on that has it's own internal buffer, and the return value has a lifetime that depends on that. Someting like fn inputln(&'a mut self, prompt: &str) -> &'a str.

@Victor-N-Suadicani
Copy link

Shouldn't there be an ownership handoff for better performance?

What would own the underlying data?

I've linked this before but I personally think my ACP suggestion is a better option for easy input. It can entirely avoid any ownership problems because you're just using the buffer from stdin and parsing directly from there. You probably want to parse quite often into a different type than String when you're getting input anyway. And you always have the option of just "parsing" into a String if that is what you want regardless.

@bazylhorsey
Copy link

Wow now that's some real code hombre.
Could you fill us in on where this is at and why your RFC was not yet accepted by the libs team

@Victor-N-Suadicani
Copy link

Wow now that's some real code hombre. Could you fill us in on where this is at and why your RFC was not yet accepted by the libs team

It's an ACP, not an RFC. I was advised to use the ACP process instead of the RFC process because it was just a single function to Stdin.

It is currently still waiting for the libs team to look at it and give a respond and it's unfortunately taking a long time. Or at least that's what I think but I can't seem to find any documentation on the ACP process any more and now I'm curious what happened to it.

@bazylhorsey
Copy link

bazylhorsey commented Dec 31, 2023

After a lot of deliberation, I aimed at simplification and producing minimal code changes while maximizing velocity for newcomers to I/O related problems. This is made somewhat with the general rust dev in mind, but not to the point it becomes an advance level feature, or fails in solving the original problem: making the Rust intro smoother.

What this board learned:

  • Problems are wide
  • Beginner devs don't want to have to understand 200-300 undergrad concepts to learn the first 30 minutes of the Rust tutorial.
  • I/O features don't need to handle 100% of advanced cases to be useful.
  • While the last point is true, there is a balance to achieve making this feature usable for beginner and experts alike.
  • MANY submitted their code. (Mine is NOT definitive), but after looking at many examples and cross-examining my own. My findings pointed me that it is better here to find the similarities and reduce, than find the union of features and culminate them.

Things this does:

  • make io casts to specific variables easier
  • leverage macros for straight-forward usage
  • remove worry of understanding the actual processes of OS I/O
  • Use common (but not overly complex) error precision
  • minimal interaction, this could be very simple in source.

Things this does NOT DO:

  • Handle concurrent or complex I/O patterns
  • Not tested with pipes and providing things like file as input, this can be discussed in the future.
use std::io::{self, Write};
use std::str::FromStr;

/// Custom error type for input parsing.
pub enum InputError {
    IoError(io::Error),
    ParseError(String),
}

/// Function to get input and parse it into the desired type.
pub fn parse_input<T: FromStr>(prompt: &str) -> Result<T, InputError>
where
    <T as FromStr>::Err: ToString,
{
    print!("{}", prompt);
    io::stdout().flush().map_err(InputError::IoError)?;

    let mut input = String::new();
    io::stdin().read_line(&mut input).map_err(InputError::IoError)?;

    input.trim().parse::<T>().map_err(|e| InputError::ParseError(e.to_string()))
}

/// Macro to simplify calling the parse_input function.
#[macro_export]
macro_rules! parse_input {
    ($prompt:expr) => {
        $crate::parse_input::<_>($prompt)
    };
}

#[cfg(test)]
mod tests {
    // Example test for successful parsing
    #[test]
    fn test_parse_input_success() {
        let result = "42".parse::<f64>();
        assert_eq!(result, Ok(42.));
    }

    // Example test for failed parsing
    #[test]
    fn test_parse_input_failure() {
        let result = "abc".parse::<i32>();
        assert!(result.is_err());
    }

}

@RustyYato
Copy link

The InputError should hold the actual error FromStr::Err. Stringly typed errors are a code smell.
Something like:

enum InputError<E> {
    Io(io::Error),
    Parse(E),
}

pub fn parse_input<T: FromStr>(prompt: &str) -> Result<T, InputError<T::Err>> { ... }

@tmccombs
Copy link

To add on to @RustyYato 's comment, when I first saw the definition of InputError, I thought that the content of the Parse variant was the value of the string that was read but failed to parse, not the stringification of the parse error.

And actually, it might be worth including the string read in that variant.

@Phosphorus-M
Copy link
Contributor

Maybe it's better to separate the topics.
An input macro could be just an Option<String>?
Meanwhile, the scanf macro could be a Result<T, ParseError<E>> ?

I said that because in my opinion, we are talking about different cases.

However, this doesn't solve the first problem that Mara said.

What does this function do when it hits EOF or reads a non-newline-terminated line?

Python's input() throws an exception on a zero read() with no input, but running inputln()? in a loop will run forever when hitting the end of a file that's redirected to stdin

Besides we must start with just one easy case (Maybe) to replace the current

let mut input = String::new();
stdin().read_line(&mut input)

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

Successfully merging this pull request may close these issues.