Reputation: 2601
This code spawns a child process, consuming its stderr and stdout line by line, and logging each appropriately. It compiles and works.
use std::error::Error;
use std::process::{Stdio};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::{Command, Child};
use tracing::{info, warn};
macro_rules! relay_pipe_lines {
($pipe:expr, $handler:expr) => {
tokio::spawn(async move {
let mut reader = BufReader::new($pipe).lines();
loop {
let line = reader
.next_line()
.await
.unwrap_or_else(|_| Some(String::new()));
match line {
None => break,
Some(line) => $handler(line)
}
}
});
};
}
pub fn start_and_log_command(mut command: Command) -> Result<Child, Box<dyn Error>> {
command.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = command.spawn()?;
let child_stdout = child.stdout.take().unwrap(); // remove `take` from here
let child_stderr = child.stderr.take().unwrap(); // .. or from here and it fails
let child_pid = child.id().unwrap();
relay_pipe_lines!(child_stdout, |line|info!("[pid {}:stdout]: {}", child_pid, line));
relay_pipe_lines!(child_stderr, |line|warn!("[pid {}:stderr]: {}", child_pid, line));
Ok(child)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init();
info!("Tracing logging initialised.");
let mut command = Command::new("ls");
command.arg("-l");
let mut child = start_and_log_command(command)?;
// Compose reading waiting concurrently.
let exit_status = child.wait().await.expect("Cannot reap child process");
dbg!(exit_status.success());
Ok(())
}
Removing the call to take()
from the indicated lines fails the build, as "child.stdout
partially moved due to this method call", which I mostly understand.
I'd like to understand how using take()
does not partially move child.stdout
.
Upvotes: 1
Views: 547
Reputation: 155196
It's a call to Option::take()
, which avoids a "partial move" by leaving None
in place of the moved value. As a result, child
is left in a valid state and can be returned from the function.
In other words, child.stdout.take()
is equivalent to std::mem::replace(&mut child.stdout, None)
, and means "take the current value out of the option (whatever it is), and leave None
in its place."
Upvotes: 10