Search code examples
rustterminalio

Counter only displays at 10Hz if it's printed with eprint! to stderr vs. print! to stdout


I wrote a program to simply increment a counter and display it in the terminal window:

use std::thread;
use std::time;

fn main() {
    let mut bun: u64 = 0;

    loop {
        print!("\r{}", bun);

        bun += 1;

        thread::sleep(time::Duration::from_millis(100));
    }
}

I thought this was pretty straight-forward (expecting to see the counter incrementing at ~10 times per second) but every time I compile and run it, what I get is the following:

  • Nothing at all displays for ~28 seconds (just a blank line with a cursor)
  • Terminal displayed 282, followed by another long delay
  • Terminal displays 538, another delay
  • Then 794, etc., etc.

It seems pretty clear to me that the counter is incrementing properly, but that the print! macro is only being called (or working) every 28 or so seconds.

On a hunch I tried eprint! in place of print! and this ends up working exactly as I would/did expect originally (i.e. the terminal displays the counter incrementing 10 times per second).

What I can't figure out, then, is why this would work with one and not the other (I was under the impression that printing to io::stderr and io::stdout would be basically the same when using a terminal window). Is there some subtle difference between them that I am not grasping? Is there something else in my code that is causing this that I'm not seeing? I Googled around a bit but couldn't seem to turn up anything relating specifically to this issue.

I am using:

  • Rust version 1.53.0
  • Apple M1 on OS 11.4 (and assuming this means I am compiling to aarch64-apple-darwin)
  • Compiling/running using cargo run --release

Solution

  • As mentionned in the documentation for print!, the standard output is line-buffered, which means updates aren't delivered to the terminal after every call. Standard error doesn't seem buffered the same way, so you get immediate updates.

    To get the same behaviour on stdout, add a call to std::io::stdout().flush() after printing.

    use std::io::Write;
    use std::thread;
    use std::time;
    
    fn main() {
        let mut bun: u64 = 0;
    
        loop {
            print!("\r{}", bun);
            std::io::stdout().flush().unwrap();
    
            bun += 1;
    
            thread::sleep(time::Duration::from_millis(100));
        }
    }