Adrián Delgado
Adrián Delgado

Reputation: 190

Idiomatic way of mimicking Python's input function in Rust

I have two three different versions of a function that mimics the input function from python.

use std::io::{self, BufRead, BufReader, Write};

// Adapted from https://docs.rs/python-input/0.8.0/src/python_input/lib.rs.html#13-23
fn input_1(prompt: &str) -> io::Result<String> {
    print!("{}", prompt);
    io::stdout().flush()?;
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer)?;
    Ok(buffer.trim_end().to_string())
}

// https://www.reddit.com/r/rust/comments/6qn3y0/store_user_inputs_in_rust/
fn input_2(prompt: &str) -> io::Result<String> {
    print!("{}", prompt);
    io::stdout().flush()?;
    BufReader::new(io::stdin())
        .lines()
        .next()
        .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Cannot read stdin"))
        .and_then(|inner| inner)
}

// tranzystorek user on Discord (edited for future reference)
fn input_3(prompt: &str) -> io::Result<String> {
    print!("{}", prompt);
    std::io::stdout().flush()?;
    BufReader::new(std::io::stdin().lock())
        .lines()
        .take(1)
        .collect()
}

fn main() {
    let name = input_1("What's your name? ").unwrap();
    println!("Hello, {}!", name);
    let name = input_2("What's your name? ").unwrap();
    println!("Hello, {}!", name);
    let name = input_3("What's your name? ").unwrap();
    println!("Hello, {}!", name);
}

But they seem to be very different aproaches and I don't know if there's any advantage using one over the other. From what I've read, having a function like python's input is not as simple as it seems which is why there's none in the standard library.

What problems could I face using any of the versions written above? Is there another, more idiomatic, way of writing this input function? (2018 edition)

Also, here: How can I read a single line from stdin? some of the answers use the lock() method but I don't get its purpose.

I'm learning Rust coming from python.

Upvotes: 3

Views: 1022

Answers (1)

Richard Matheson
Richard Matheson

Reputation: 1175

This is a question of style mostly - both methods are acceptable. Most of the Rustaceans I know would probably favour the second approach, as it's more functional in style but it really doesn't matter in this case.

The key change I'd make is use of the lock method in your second example.

To understand the lock method, consider the following scenario: if you application was multithreaded, and two threads attempted to read from stdin at the same time, what would happen?

The lock ensures that only one thread can access stdin at a time. You always access stdin through a lock. In fact, if you look at the implementation of Stdin::read_line - the method you call in the first example you'll see it's this very simple one-liner:

self.lock().read_line(buf)

So even when you aren't explicitly calling lock it's still being used behind the scenes.

Secondly .next() won't return None in this case, as it will block until data has been entered, so you can use .unwrap() safely here rather than .ok_or/.and_then.

Lastly you missed out the .trim_end() that you had in input_1 ;).

fn input_2(prompt: &str) -> io::Result<String> {
    print!("{}", prompt);
    io::stdout().flush()?;
    io::stdin()
        .lock()
        .lines()
        .next()
        .unwrap()
        .map(|x| x.trim_end().to_owned())
}

Upvotes: 3

Related Questions