You must have tried to create a new rust project with cargo:
cargo new
and the generated main.rs is the most straightforward:
fn main() {
println!("Hello, world!");
}
With cargo run
it does the expected thing:
Compiling rust-test v0.1.0 (/home/fam/test/rust-test)
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/rust-test`
Hello, world!
However, rust is quite peculiar in how its standard library, particularly, the println!
macro above, deals with stdout.
How is that? If your stdio is unusually set to non-blocking mode, the above can panic!
To demostrate it, let’s move the print into a loop to make the program run longer, while we do something with the terminal. Change the main function to:
fn main() {
loop { println!("Hello, world!"); }
}
This will create a deadloop and print infinite lines of “Hello, World!” in your terminal. Let’s run it; once we see the output, press Ctrl-Z
to stop. Then we can run bg
command to let it continue in the background. It will continue to print quickly, until we type:
tmux<Enter>
Note that you cannot see the echo when you type because the screen is flushed by the hello world output. That doesn’t matter, just press the five keys blindly.
Did you notice the flooding is stopped? If you quit tmux (ctrl-d), you can see this line on your screen:
[1]+ Exit 101 cargo run
Our hello-world has exited. Something went wrong.
The reason is that a non-blocking tty can report busy while being written to by the hello world. Normally this doesn’t happen, but since both tmux and hello world share the same console terminal, and tmux is designed to set stdandard input to non-blocking mode in the beginning, it affects the standard output file descriptor of our hello world program too.
And the returned error (errno -EAGAIN), indicate the writer should retry later (ideally after polling the fd and getting a writable flag). Unfortunately rust stdlib (print_to
) doesn’t understand this error because the non-blocking mode is considered “not supported”.
There is a github issue discussing this:
TL;DR: you’re on your own if you have non-blocking stdio.
The conclusion is if you write Rust, use println!
, or if a library you pull in (direcrtly or indirectly) uses println!
, good luck with it! You probably have no control over the stdio regarding non-blocking flag, and to make it worse, it can change any time, like shown in the example above, which is all up to the user or sys admin. If that happens, your program is prone to crash and there is very little trace to analyse the failure because even the panic!
info which is supposed to be on the stderr may not be printed for the same reason.
This is quite frustrating, because I always thought it’s somewhat easier to write robust programs with Rust, though this issue and the unhelpful replies from the rust deveopers are kind of indicating opposite.
To be fair, other languages can suffer from the same problem, in different degrees.
For example in C++, std::cout will drop the unwritten part upon -EAGAIN and set fail() flag; in C the number of written bytes is always returned by printf
so there is also room for explicit error handling.
Python would throw an exception called BlockingIOError which is easy to handle in a try-catch block. Go will automatically retry so it “magically” just works, but potentially it can cause unnecessary busy loop if the busy status stays there for long.
In rust, the remedy will be always using writeln!
instead of println!
if you want to tolerate non-blocking stdio and not crash that easily; but that doesn’t help others’ code from crates.io which you don’t own.