Tokio (I'm using 0.2, with futures 0.3) has process::Command.output()
, which returns the combined output (both stdout and stderr) from a child process after it's complete. However, I would like to stream the output for a few reasons:
It might be larger than available memory.
The stdout and stderr output may be interleaved (I realise that in general the relationship between the two will not be something that can be relied upon, but I'd rather make as much of an effort as possible to keep the results as similar as running the process normally).
I don't want to wait for the process to terminate before output starts being returned.
I tried using select!
with a pair of futures and a loop:
let outbuf = [0u8; 512];
let errbuf = [0u8; 512];
let outfut = child_stdout.read(&mut outbuf).fuse();
let errfut = child_stderr.read(&mut errbuf).fuse();
pin_mut!(outfut, errfut);
loop {
select! {
result = outfut => {
let length = result?;
if length != 0 {
output.write_all(&outbuf[..length]).await?
outfut.set(child_stdout.read(&mut outbuf).fuse())
}
}
result = errfut => {
let length = result?;
if length != 0 {
output.write_all(&errbuf[..length]).await?
errfut.set(child_stderr.read(&mut errbuf).fuse())
}
}
complete => break
}
}
let status = child.await?
Of course, this won't work because I've borrowed the buffers twice; I've tried rearranging the code in various ways, but when using select!
like this I always seem to end up having to borrow something twice. (Note also that in practice there's a little more output in the if
statements than I'm showing above, and the stdout and stderr branches differ from one another, so you can't just redirect both stdout and stderr to the same place and call it a day.)
Clearly this is not the right approach. Is there an idiomatic Rust way to achieve what I'm after here?