3

[Draft] RFC: Console Input Simplified by undersquire · Pull Request #3183 · rust...

 2 years ago
source link: https://github.com/rust-lang/rfcs/pull/3183
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Copy link

undersquire commented 11 days ago

edited

Implement simple macros to make console input simple, just like how console output via println is very simple and convenient.

Rendered

Copy link

m1ten commented 11 days ago

edited

I like the idea. Input and output would be more consistent if input! gets implemented.

Copy link

Member

kennytm commented 10 days ago

Copy link

Author

undersquire commented 9 days ago

edited

see also:

In the second link, they closed the issue with the message: This was discussed in today's meeting and it was decided that a feature such as this should bake in a library before being accepted into the main repo, so I'm going to close this for now.

This feature has been baking in several libraries (such as text_io) for several years, so maybe I could suggest the version from one of these crates?

Copy link

dlight commented 7 days ago

edited

I think inputln!() doesn't need to be a macro, since it would work just as well if it were a function (maybe with #[inline]). Nonetheless, I think it's really important to have something like this in the stdlib for writing short snippets of code. I mean, here's the most compact code I can think of that does the same thing (well, sans error handling) without needing multiple statements:

use std::io::BufRead;

std::io::stdin().lock().lines().next().unwrap().unwrap()

Also, being able to write this kind of thing without having to call Stdin::read_line() is also important for teaching, because it lets us read lines from stdin working purely with value semantics, before we learned even what a &mut is. Indeed after teaching inputln!(), the teacher could then show how the pros do it, and explain that while Stdin::read_line() is a more complicated API, there's a benefit if we want to reuse the String buffer for reading multiple lines.

Copy link

dlight commented 7 days ago

Something that is not clear in the guide-level explanation of input!() is that it reads the whole line. On a first impression, I would expect it to read up to the next whitespace, at least for numbers. that way, one could call input!(i32) multiple times to read numbers in the same line. (this was my expectation at first because that's what C's scanf("%d") does).

That's specially confusing because input!() and inputln!() seem to be analogous to print!() and println!(), but print!() is just a println!() that prints without a newline. So it's logical to expect that input!() reads without a newline, too. Indeed, from the naming alone, what I expected is that inputln!() also received a type parameter, so that inputln!(i32) reads a line and parses it as i32, and input!(i32) reads until the first whitespace. inputln!() without a parameter could still work though, and be equivalent to inputln!(String).

But I think this just means that input!() and inputln!() are bad names, or, at least, the names don't mirror print!() and println!() as much as one would like.

And well, scanf is arguably a bad API, and it does the wrong thing when running scanf("%s") - it reads until the first whitespace, which is a common a newbie trap, and most programs that read strings from stdin want to read the whole line (and then maybe split it afterwards and treat each piece separately), because a tty is line-buffered. So input!(String) reading only up to the first whitespace character is the wrong thing to do.

Copy link

chrysn commented 7 days ago

A reason for Python's prompt line is that the input needs to be flushed. Just going for

    print!("Name? ");
    let name = input!(String);

doesn't show the prompt until after the user has pressed Return. A println! works, but given that input-in-the-same-line is a style that's frequent in interactive CLI tools, it doesn't quite cut it.

Copy link

chrysn commented 7 days ago

On the "drawback" side, I'd add that it is putting a possibly sub-optimal solution into the standard library.

Printing to stdout is straightforward, and print/println can cover the common operations with good quality, possibly even with color, up to the point where there is binary data or you want fancy line rewriting.

Reading from stdin does not have an analogon for "simple and does almost everything you want": The terminal emulator usually gives some line editing capability (like erasing the last character), but even moving back with the cursor to change anything but the last letter is not provided. The Python version you cite for comparison does not do that either, but as soon as the Python readline module is loaded (which is the case when the interactive interpreter is used), readline's input takes over Python's input function and provides not only cursor movement but also history.

Other than first-week introduction-to-programming examples, I think that most developers will have unhappy users with a bare terminal input, and would be better off using rustyline or something like that right away.

Copy link

Author

undersquire commented 6 days ago

Something that is not clear in the guide-level explanation of input!() is that it reads the whole line. On a first impression, I would expect it to read up to the next whitespace, at least for numbers. that way, one could call input!(i32) multiple times to read numbers in the same line. (this was my expectation at first because that's what C's scanf("%d") does).

That's specially confusing because input!() and inputln!() seem to be analogous to print!() and println!(), but print!() is just a println!() that prints without a newline. So it's logical to expect that input!() reads without a newline, too. Indeed, from the naming alone, what I expected is that inputln!() also received a type parameter, so that inputln!(i32) reads a line and parses it as i32, and input!(i32) reads until the first whitespace. inputln!() without a parameter could still work though, and be equivalent to inputln!(String).

But I think this just means that input!() and inputln!() are bad names, or, at least, the names don't mirror print!() and println!() as much as one would like.

And well, scanf is arguably a bad API, and it does the wrong thing when running scanf("%s") - it reads until the first whitespace, which is a common a newbie trap, and most programs that read strings from stdin want to read the whole line (and then maybe split it afterwards and treat each piece separately), because a tty is line-buffered. So input!(String) reading only up to the first whitespace character is the wrong thing to do.

Yes that is why in the RFC I state that it would be better for input! to use more of a C++ std::cin approach, where it reads to the next space rather than the whole line: One thing I would probably do differently in this implementation is, for the input!(TYPE); macro, make it so it only reads from stdin until it hits a (space), similar to how C++'s std::cin works.

However, I have been revising my RFC and trying to come up with a better API, and found the one found in the text_io crate to be quite useful and powerful. Maybe we can go with it's approach?

Copy link

Author

undersquire commented 6 days ago

On the "drawback" side, I'd add that it is putting a possibly sub-optimal solution into the standard library.

Printing to stdout is straightforward, and print/println can cover the common operations with good quality, possibly even with color, up to the point where there is binary data or you want fancy line rewriting.

Reading from stdin does not have an analogon for "simple and does almost everything you want": The terminal emulator usually gives some line editing capability (like erasing the last character), but even moving back with the cursor to change anything but the last letter is not provided. The Python version you cite for comparison does not do that either, but as soon as the Python readline module is loaded (which is the case when the interactive interpreter is used), readline's input takes over Python's input function and provides not only cursor movement but also history.

Other than first-week introduction-to-programming examples, I think that most developers will have unhappy users with a bare terminal input, and would be better off using rustyline or something like that right away.

I don't think they would be unhappy, I think they would simply just install a more complex crate if they need more features. Having a simplified input system would simply make input for smaller programs, teaching, etc easier. I understand what you mean however.

Copy link

chrysn commented 6 days ago

The users are in no position to updated that -- unlike Python where you can just import readline around the program, when a developer uses the std input the users are stuck with that. And these users would be stuck in a three-party triangle of

  • users who are told that this is good enough but see helllo^[[world on screen and yet different program behavior,
  • developers who don't want to use an external crate when the standard library already does it, and
  • the standard library team that is asked to "just make it bettter" (which is not trivial portably, which is exactly why things like readline exist).

I wouldn't want to be in either of these three situations.

If we go ahead with this, I think that the input functions should described as "for experimentation and educational purposes", and their documentation should recommend using platform specific method or third party abstractions when not falling into these categories (possibly with concrete suggestions). Then the above deadlock has a clear way out.

Copy link

Author

undersquire commented 6 days ago

The users are in no position to updated that -- unlike Python where you can just import readline around the program, when a developer uses the std input the users are stuck with that. And these users would be stuck in a three-party triangle of

  • users who are told that this is good enough but see helllo^[[world on screen and yet different program behavior,
  • developers who don't want to use an external crate when the standard library already does it, and
  • the standard library team that is asked to "just make it bettter" (which is not trivial portably, which is exactly why things like readline exist).

I wouldn't want to be in either of these three situations.

If we go ahead with this, I think that the input functions should described as "for experimentation and educational purposes", and their documentation should recommend using platform specific method or third party abstractions when not falling into these categories (possibly with concrete suggestions). Then the above deadlock has a clear way out.

Responding to the three-party triangle:

  1. The input! macro simply reads in a value from the console, and this unexpected behavior you gave as an example exists in every language's implementation of basic console input (arrow keys writing ^[[), so I am not sure this is at all a big deal.

  2. This is not really the case. In fact, I see this as potentially a good thing. Like imagine needing to add an entire crate, increasing compile time just for something as small as println!. It is inconvenient, especially when the standard library could probably easily implement such a feature. On the other hand, people use crates over things from the standard library all the time, a big one would be asynchronous programming. Not many people write async/concurrent code using just the standard library (even though it's 100% possible and not really that difficult), you are almost guaranteed to add tokio to your dependencies or another crate. Another example is argument parsing. You can do this just fine with the standard library, but there are crates like clap for more features when needed.

  3. I am not really sure people are going to complain about a console input feature; I don't see people complaining often about println or anything like that. Either way, if we provide a good API (not saying mine is the solution, in fact I am revising my concept for it right now to change and improve it), people will probably be happy enough with it.

This is just like the println macro, it can easily be replaced by a logging crate and often is in many projects. Same with how this input! macro would be able to be replaced in many projects with crates like rustyline. The point of this macro is that it makes it more immediate to start handling input, whether used in a learned/teaching area or maybe professionally in an actual project, it still makes sense to me to offer it regardless.

Copy link

Author

undersquire commented 5 days ago

edited

I have just pushed a commit to this RFC improving the example implementation. It now only reads up to a space for input!(TYPE) and works quite well. For instance, if I input 10 10, and the program calls input!(i32); twice, it will obtain both integers. If I wrote 10\n10 (\n being me pressing enter), and call input!(i32); twice, I will still get both integers.

Please let me know what you think of these changes.

Copy link

m1ten commented 5 days ago

edited

See also rust-lang/rust#75435 and this draft RFC.

While there are other RFCs related to #3183, none of them solved the problem.

Copy link

HKalbasi commented 2 days ago

This RFC is great for using rust in competitive programming. People still widely use C/C++ in that field, because languages such as python or java will exceed time/memory limits in hard problems. So Rust is a great fit for this field, but lack of a good input system blocks the usage of Rust. And this RFC is the only possible kind of solution for it because:

  • Even something like inputln!() is suboptimal and not enough, because these problems are designed with scanf and cin in mind, and you don't want to spend your time writing code that parses spaces.
  • Third party crates are not option, because your code will be compiled in the judge servers, so you need to convince the platform owners to include this third party crate with every code. It is not impossible, but very hard. There are too many platforms and competing crates for some work (and you should not include too many crates, for fair competition with other languages), so it is not practical. With this being included in the std, the problem is solved.

Other than first-week introduction-to-programming examples, I think that most developers will have unhappy users with a bare terminal input, and would be better off using rustyline or something like that right away.

If scanf or cin or input works for people in other languages, this would work for people in Rust similarly. I doubt if only first week programmers are happy with scanf and cin in other languages.

Copy link

Author

undersquire commented 2 days ago

This RFC is great for using rust in competitive programming. People still widely use C/C++ in that field, because languages such as python or java will exceed time/memory limits in hard problems. So Rust is a great fit for this field, but lack of a good input system blocks the usage of Rust. And this RFC is the only possible kind of solution for it because:

  • Even something like inputln!() is suboptimal and not enough, because these problems are designed with scanf and cin in mind, and you don't want to spend your time writing code that parses spaces.
  • Third party crates are not option, because your code will be compiled in the judge servers, so you need to convince the platform owners to include this third party crate with every code. It is not impossible, but very hard. There are too many platforms and competing crates for some work (and you should not include too many crates, for fair competition with other languages), so it is not practical. With this being included in the std, the problem is solved.

Other than first-week introduction-to-programming examples, I think that most developers will have unhappy users with a bare terminal input, and would be better off using rustyline or something like that right away.

If scanf or cin or input works for people in other languages, this would work for people in Rust similarly. I doubt if only first week programmers are happy with scanf and cin in other languages.

Yeah this is another good example of where having a simplified input system would be useful. My concept for the implementation is also just a mere example, I was thinking that it might be better to implement the input functions/macros that an existing crate has like text_io, as it supports more advanced input formatting. What do you think of that?

undersquire

marked this pull request as draft

yesterday

Copy link

Author

undersquire commented yesterday

I decided to mark this as a draft since we are still deciding on an implementation. Please suggest any ideas if you have any.

undersquire

changed the title RFC: Console Input Simplified

[Draft] RFC: Console Input Simplified

yesterday


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK