Can I use std::process::Child with stdin, stdout and stderr at all?


#1
let mut comm = Command::new("sh")
    .arg("-c")
    .arg("for i in $(seq 1 3); do sleep 1; echo line $i; done")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn().unwrap();

let stdout = BufReader::new(comm.stdout.unwrap());
let stderr = BufReader::new(comm.stderr.unwrap());
let stdin = BufWriter::new(comm.stdin.unwrap());

&comm.wait();

This won’t compile with this error:

error: use of partially moved value: `comm`
    &comm.wait();

and I don’t find a way to do. Problem is: BufReader::new() consumes comm.
I don’t understand how to use them at all.


#2

You can destruct the Child struct using let to obtain ownership of all 3 variables.

let Child {
    stdin: stdin_raw,
    stdout: stdout_raw,
    stderr: stderr_raw,
    .. } = Command::new("sh")
    .arg("-c")
    .arg("for i in $(seq 1 3); do sleep 1; echo line $i; done")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn().unwrap();

let stdin = BufWriter::new(stdin_raw.unwrap());
let stdout = BufReader::new(stdout_raw.unwrap());
let stderr = BufReader::new(stderr_raw.unwrap());

Check out https://doc.rust-lang.org/book/patterns.html#destructuring. The examples there use match, but it works exactly the same on the left side of a let expression (before the =).


#3

Ah, thank you!

Can I still use the Child struct (as a whole) to call .wait() or .kill() on it?

As far as I understood I cannot assign a name to the way you destructured Child here, so I build this:

let mut chld = Command::new("sh")
    .arg("-c")
    .arg("for i in $(seq 1 3); do sleep 1; echo line $i; done")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn().unwrap();

let Child {
    stdin: stdin_raw,
    stdout: stdout_raw,
    stderr: stderr_raw,
    ..
} = chld;

let stdin = BufWriter::new(stdin_raw.unwrap());
let stdout = BufReader::new(stdout_raw.unwrap());
let stderr = BufReader::new(stderr_raw.unwrap());


chld.wait();

Which fails on the last line with

error: use of partially moved value: `chld`

I understand that it has to fail here, because I used part of chld (namely its stdin/stdout/stderr). But I don’t know how I can call .wait() on the child process.
I can’t use the borrow operator & (because of BufWriter::new() and BufReader::new()) here, and as far as I understand neither Borrow nor AsRef will help here.

EDIT:
And copying is no option either since it is not supported by Child or Stdio.


#4

You can use Option::take instead of destructuring:

let stdout = BufReader::new(comm.stdout.take().unwrap());
let stderr = BufReader::new(comm.stderr.take().unwrap());
let stdin = BufWriter::new(comm.stdin.take().unwrap());

comm.wait();

#5

With the way I showed, no, you are basically destroying the Child struct. @stebalien’s method is much more reasonable in that regard - instead of just completely taking the Options, it takes the Some() value and replaces it with a None in the struct - allowing the Child to still be used.


#6

Thank you, works now and (more importantly) I understood why it didn’t.