3

I'm an absolute Rust beginner trying to build a simple confirmation function (yes or no), but I can't get the user to type anything, the function just keeps looping without waiting for user input:

""
""
""
etc.  

is the result of the simplified version below.

use std::process;
use std::io;

pub fn confirm() {
  loop {
    let mut answer = String::new();

    io::stdin().read_line(&mut answer)
      .ok()
      .expect("Failed to read line");

    println!("{:?}", answer);
  }
}

I've built my function around the guessing game example, and the rest of my program does nothing much, just reading a file and printing text.

Perhaps is due to the way my program (a git hook) is launched?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nicompte
  • 243
  • 9
  • 20
  • What kind of hook? There seems to be tricks with [commit message hooks](http://stackoverflow.com/q/3417896/155423), for example. – Shepmaster Sep 19 '15 at 12:56

2 Answers2

3

Are you testing the function on the Rust Playground? Running this program in a terminal seems to work fine. That being said, there is no guarantee that stdin will block, but you could change the function to check if the string is empty or not, and only return once it is isn't.

use std::io;

fn main() {
    println!("{:?}", confirm());
}

fn confirm() -> String {
    loop {
        let mut answer = String::new();

        io::stdin().read_line(&mut answer)
                   .ok()
                   .expect("Failed to read line");

        if !answer.is_empty() && answer != "\n" && answer != "\r\n" {
            return answer
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
XAMPPRocky
  • 3,160
  • 5
  • 25
  • 45
  • You should also possibly `thread::sleep(..)` a bit in your loop, otherwise this may busy-wait if the read_line doesn't block. – llogiq Sep 19 '15 at 10:51
  • Like I said I've never had it not block, on both windows and OSX. Also from the documentation it doesn't say that if it doesn't block once, it won't block the next time. – XAMPPRocky Sep 19 '15 at 12:48
  • Fair enough, but nowhere does it guarantee that it will block at all. In that case, your program will just spin in the loop until someone kills it. – llogiq Sep 19 '15 at 13:35
  • The `.ok()` before the `.expect(…)` is superfluous. – Kaplan Aug 20 '22 at 19:57
3

Assuming that the problem is that your git commit hook is running in an non-interactive environment, you can follow the advice laid out in that question and directly open /dev/tty. Unlike STDIN, we don't treat it as a magical global variable and instead we pass it into the places we need:

use std::io::{self, BufRead, BufReader};
use std::fs::File;

type Tty = BufReader<File>;

fn open_tty() -> io::Result<Tty> {
    let f = try!(File::open("/dev/tty"));
    Ok(BufReader::new(f))
}

fn confirm(tty: &mut Tty) -> io::Result<String> {
    let mut answer = String::new();
    try!(tty.read_line(&mut answer));
    Ok(answer)
}

fn inner_main() -> io::Result<()> {
    let mut tty = try!(open_tty());
    let answer = try!(confirm(&mut tty));

    println!("The answer was: {}", answer);

    Ok(())
}

fn main() {
    inner_main().unwrap()
}

Note that this will not be platform independent. Specifically, this is very unlikely to work on Windows!

I've also gone ahead and allowed the io::Result to propagate throughout the program, only panicking at the outermost shell.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366