Rust has abstracted processes as a class that models a sub-process, which is mentally easier to play with compared to C’s explicit fork() and exec().

In Rust, std::process::Command::new() wraps up fork(), execve(), and returns a class that models this subprocess.

For inter-process communication, Stdio::piped() creates a pipe pipe() and duplicates the pipe with dup2() to redirect the stdin, stdout, stderr to accept inputs, get outputs, etc.

Command::new(), args(), stdout()
1
2
3
4
5
6
7
8
9
pub fn run_command(program: &str, args: &[&str]) -> String {
let result = Command::new(program)
.args(args)
.stdout(Stdio::piped())
.output()
.unwrap()
.stdout;
String::from_utf8(result).unwrap()
}

In the above example, we created a subprocess to run program with arguments args through method .args(). We also redirected stdout using .stdout(Stdio::piped()) in order to capture its output with .output().unwrap().stdout

consume stdin & stdout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pub fn pipe_through_cat(input: &str) -> String {
let mut cat = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
cat.stdin
.take()
.unwrap()
.write_all(input.as_bytes())
.unwrap();

let mut output = String::new();
cat.stdout
.take()
.unwrap()
.read_to_string(&mut output)
.unwrap();
cat.wait().unwrap();
output
}

The key points are

  • stdxxx.take() extract out the inner object, leaving None in-place
  • all stdins and stdouts should be dropped before process.wait()