Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

What is `take()` doing here, and why do I need it?

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

It’s a call to Option::take(), which avoids a "partial move" by leaving None in place of the moved value.

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."

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading