Pre-RFC: input! macro


#1

Python has really convenient functions for interacting with the user over the command line. The print() function for printing to stdout with a newline by default, and the input() function to get a line from stdin with an optional prompt to the user.

While Rust has convenient equivalents to Python’s print() function, print! and println!, there is no such equivalent to input(). I think there should be. It would make interacting with the user so much easier, which is important for demonstrating the power and ease of use of a language to beginners.

Currently, getting a line from stdin is quite clunky, and does not include the ability to prompt the user:

fn main() {
    print!("Input your age: ");
    let input = std::io::stdio::stdin().read_line().unwrap();
}

Getting multiple inputs is even clunkier, because the recommended practice is retaining the buffered handle so there’s no extra allocation:

fn main() {
    let mut stdin = std::io::stdio::stdin();

    print!("Input your name: ");
    let name = stdin.read_line().unwrap();

    print!("Input your age: ");
    let age = stdin.read_line.unwrap();
}

This requires knowledge of how Writers work, knowledge of mutability, awareness of how modules work and of std::io::stdio::stdin(). This delays the use of user input in beginner examples until all of the above is covered, or else we run the risk of confusing and discouraging the fledgling Rustacean. It’s also very noisy in general.

An input macro would make this so much simpler:

fn main() {
    let name = input!("Input your name: ");
    let age = input!("Input your age: ");
}

The last block of code is the most clear and concise, and can be taught right alongside usage of print! and println!, making beginning code examples much more interactive. It should take formatting arguments for printing the prompt just like its output counterparts. Called with no arguments, it should print nothing and wait for a line.

This macro can fail internally on unwrap, but print! and println! also fail internally if there was an error printing to stdout, so it’s not really a new issue.

This shouldn’t be a breaking change, except for code that uses a third-party input! macro, which would likely be redundant with this change anyways.

##Pros

  • Improve interactiveness in beginner examples
  • Reduce noise and boilerplate in code that interacts bidirectionally (input and output) with the user

##Cons

  • Another macro to implement, maintain, and teach
  • Still requires type conversion for any input other than String.
    • Type conversion should be taught early anyways.

##Alternatives

  • Do nothing
    • All existing problems with the current approach remain, but no additional effort required.
  • A non-allocating input_line() function exported in prelude that returns String.
    • Still requires separate prompting

#2

Why does this need to be a macro? println! needs to be a macro because it takes a variable number of arguments and does typechecking based on the contents of a string literal. What’s input doing that a function can’t? And how does it resolve the handle-efficiency issue?


#3

I just added that the macro should take formatting arguments for printing the prompt. For example:

let default = 5u;

let input = input!("Enter the number of apples to buy ({}): ", default);

let count = from_str::<uint>(input[]).unwrap_or(default);

That’s not a very good example but I’m sure the functionality would see quite a bit of use.

print! uses a private task-local handle to stdout so it doesn’t have to allocate on every invocation:

http://doc.rust-lang.org/src/std/home/rustbuild/src/rust-buildbot/slave/nightly-linux/build/src/libstd/io/stdio.rs.html#201

input! could do the same thing with stdin, and use with_task_stdout to print the prompt.


#4

I had an RFC for something similar a while back - https://github.com/rust-lang/rfcs/pull/67. The conclusion was that we should implement something and get some experience before adding it to the libraries. I.e., the implementation should precede the RFC. I think the same thing holds for input!.

I did actually start an implementation, but it was hard work refactoring format! and I ran out of time. I should pick this up again at some point…


#5

I think your RFC extended a bit too far. That’s probably why they wanted to see it in a library first. This is one macro for a specific, but common use that fits into the existing API.