Search code examples
rustrust-macros

print! macro gets executed out of order


A portion of my code looks like this:

print_usage_instructions();
print!("Command: ");
let stdin = io::stdin();
let mut line = String::new();
stdin.lock().read_line(&mut line).expect("Couldn't process the command.");
println!("{}", line);

The behaviour I expect here is something like this:

Usage instructions and stuff
Command: [my command]
[my command]

However, what happens is this:

Usage instructions and stuff
[my command]
Command: [my command]

Any ideas why would that happen? AFAIK, there's no reason for the compiler to change the execution order here, this part of the code is not async nor multithreaded.


Solution

  • The problem: print!() doesn't flush stdout!

    What does flushing mean, you ask? When printing, you don't want to send every character to stdout on its own: this would occur a lot of overhead (think: doing a syscall, the terminal would have to update its view, ...). So instead of doing that, there is exists a buffer somewhere which holds the content which is about to be printed. To actually print, this buffer has to be flushed.

    The reason you hardly notice all of this is, that stdout is always flushed when a newline ('\n') is printed. Thus, println!() always flushes!

    Your use case is even more confusing, because you are typing into stdin. Here it works pretty much the same way: when you type, the characters are not yet send anywhere! Only the terminal/shell stores what you typed. But once you hit enter (newline), your written text is submitted and send to stdin.

    Anyway, you can manually flush stdout without printing a newline:

    use std::io::{self, BufRead, Write};
    
    fn main() {
        println!("Usage instructions and stuff");
    
        print!("Command:");
        io::stdout().flush().expect("Couldn't flush stdout");   // <-- here
    
        let stdin = io::stdin();
        let mut line = String::new();
        stdin.lock().read_line(&mut line).expect("Couldn't process the command.");
        println!("{}", line);
    }
    

    This behavior was criticized before: "print! should flush stdout".


    And please note: your string line contains a newline character at the very end. You can remove it with trim_right(). This has nothing to do with your original question, but you are likely to run into this problem as well ;-)