🎓 A1Learner
🔍

Rust Child Processes

A child process is a program started by another program. The parent collects what the child prints.

Run a program and collect what it prints

Command::new names the program, .arg adds one argument, and .output() runs it and waits:

let output = Command::new("echo").arg("hello from a child").output().unwrap();
println!("success = {}", output.status.success());

The printed output comes back as bytes

output.stdout is raw bytes. String::from_utf8_lossy turns them into text you can print:

let words = String::from_utf8_lossy(&output.stdout);
print!("child said: {words}");

spawn starts now, wait ends it

spawn() returns a Child handle at once, while the program runs. wait() pauses until it ends and hands back the ExitStatus:

let mut child = Command::new("echo").arg("second run").spawn().unwrap();
let status = child.wait().unwrap();
println!("waited, success = {}", status.success());

Talk to the child through pipes

With stdin piped, you write to the child. Then drop the stdin handle to close it. Now the child knows the input is over:

let mut child = Command::new("tr").args(["a-z", "A-Z"]).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn().unwrap();
child.stdin.take().unwrap().write_all(b"shout back").unwrap();
let out = child.wait_with_output().unwrap();
print!("child replied: {}", String::from_utf8_lossy(&out.stdout));

Chain two programs with a pipe

The first child's stdout becomes the second child's stdin.

let mut echo = Command::new("echo").arg("hello pipes").stdout(Stdio::piped()).spawn().unwrap();
let echo_out = echo.stdout.take().unwrap();
let out = Command::new("tr").args(["a-z", "A-Z"]).stdin(Stdio::from(echo_out)).output().unwrap();
echo.wait().unwrap();
print!("piped: {}", String::from_utf8_lossy(&out.stdout));

Send the output to a file instead

A File wrapped in Stdio::from is a legal stdout — and stderr too.

let file = File::create("child_out.txt").unwrap();
let file2 = file.try_clone().unwrap();
Command::new("echo").arg("saved words").stdout(Stdio::from(file)).stderr(Stdio::from(file2)).status().unwrap();
print!("file holds: {}", std::fs::read_to_string("child_out.txt").unwrap());

Example

use std::process::Command;
fn main() {
    let output = Command::new("echo").arg("hello from a child").output().expect("could not start echo");
    println!("success = {}", output.status.success());
    print!("child said: {}", String::from_utf8_lossy(&output.stdout));
    let mut child = Command::new("echo").arg("second run").spawn().expect("could not spawn echo");
    let status = child.wait().expect("could not wait for the child");
    println!("waited, success = {}", status.success());
}

Key points

  • Use status.code() when you need the exit code itself as a number.
  • The number comes back as an Option<i32>.
  • Some(0) is success.
  • None means the program was killed before it could report a code.
  • A child process is a program that a parent program starts and manages.

In this example, echo is the terminal's own echo program, which prints its argument and ends. The first run uses output(). Command starts echo with the argument hello from a child. It waits for it and collects the result. The status reports success, and the captured stdout — printed with print! because it already ends with a newline — shows child said: hello from a child. The second run uses spawn(). spawn() starts echo and returns a Child handle immediately. wait() then pauses until that child ends. Its status also reports success. spawn() was given no pipe, so that child shares your terminal. Its own printing goes straight to the screen. That printing is the line second run. This is why it appears before the waited line.

Note

A program that does not exist makes output() and spawn() return Err.

Starting a program can fail before the program runs at all.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does .output() give you that .spawn() alone does not?