This is traditionally called tee or split, and the more principled interface is to accept an additional writer instead of printing directly, so "printing" is not hard-coded into the function. This is especially valuable because as it stands, your current code intermingles standard output and standard error (it always prints everything to stdout).
If you want it to print lines as they are received, you might need to add a writer.flush()? after the writer.write_all()? so it forces each line to be written to the terminal. Otherwise the data will be written to stdout, but your OS may not decide to pass those bytes through to whoever is connected to stdout (e.g. your terminal) until later on.
I believe print!() and println!() do this automatically when the content being printed contains a \n because stdout is internally wrapped in a std::io::LineWriter.
Alternatively, you could leave it up to the caller to make sure their writer does the flushing.
use std::io::LineWriter;
tee(BufReader::new(stdout_pipe), LineWriter::new(io::stdout()));
It looks like you need to manually write a newline because the reader.lines() stripped it away. When you do writer.write_all(line.bytes())? the line variable only contains the bytes for "Hello, World!".
Because write() isn't guaranteed to write the whole buffer.
If you want to use write(), then you need to call it in a loop, always checking its return value, and if it's smaller than the length of the slice, then retry. That's what write_all() does, so you might as well use it instead of reinventing the retry logic yourself.
In more detail, write! uses the write_fmt method[1], which in turn uses the write_all method (as its documentation explains, too).
The basic idea is that write!(writer, format_string, args…) simply becomes writer.write_fmt(format_args!(format_string, args…)). It’s in fact one macro with very easy to understand source code .