I have a C code snippet which uses pipe()
and fork()
to communicate between parent and child process. I want to replicate it in Rust. For the POSIX APIs used in C, if there are counterparts in the Rust standard library, they are preferred.
However, these two code snippets have different behaviors. What is the root cause of these different behaviors?
// error handling is omitted for simplicity's sake
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHILD_MESS "Child: I wanna cookie\n"
#define PAR_MESS "Parent: testing...\n"
int main() {
int pipe_fd[2] = {-1, -1};
int len = 0;
char buf[100] = {'\0'};
int read_len = 0;
pipe(pipe_fd);
switch (fork()) {
case 0: // in the child
len = strlen(CHILD_MESS);
while(1) {
write(pipe_fd[1], CHILD_MESS, len);
sleep(5);
}
break;
default: // in the parent
len = strlen(PAR_MESS);
while(1) {
write(pipe_fd[1], PAR_MESS, len);
sleep(1);
read_len = read(pipe_fd[0], buf, 100);
if (read_len <= 0) {
break;
}
write(1, buf, read_len);
}
}
return 0;
}
use nix::unistd::{fork, pipe, ForkResult}; // needs extern crate `nix`
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::thread::sleep;
use std::time::Duration;
const CHILD_MSG: &str = "Child: I wanna cookie\n";
const PAR_MSG: &str = "Parent: testing...\n";
fn main() {
let (read_end, write_end): (RawFd, RawFd) = pipe().unwrap();
let mut buf: [u8; 100] = [0; 100];
let mut read_end: File = unsafe { File::from_raw_fd(read_end) };
let mut write_end: File = unsafe { File::from_raw_fd(write_end) };
match unsafe { fork() } {
Ok(res) => match res {
ForkResult::Child => loop {
write_end.write_all(CHILD_MSG.as_bytes()).expect("write");
sleep(Duration::from_secs(5));
},
ForkResult::Parent { child: _ } => loop {
write_end.write_all(PAR_MSG.as_bytes()).expect("write");
sleep(Duration::from_secs(1));
let n = read_end.read(&mut buf).unwrap();
if n == 0 {
break;
}
print!("{}", std::str::from_utf8(&buf).unwrap());
},
},
_ => (),
}
}
The expected behavior is something like:
$ gcc main.c && ./a.out
Parent: testing...
Child: I wanna cookie
Parent: testing...
Parent: testing...
Parent: testing...
Parent: testing... // Five seconds elapsed
Child: I wanna cookie
Parent: testing...
...
After execution:
One second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Two seconds elapsed: print `Parent: testing...`
Three seconds elapsed: print `Parent: testing...`
...
Five seconds elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
...
However, for the Rust code snippet I get something like this:
$ cargo run -q
Parent: testing...
Child: I wanna cookie
Parent: testing...
Child: I wanna cookie
...
After execution:
One second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Two second elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
Three seconds elapsed: print `Parent: testing...\nChild: I wanna cookie\n`
...
It just print Parent: testing...\nChild: I wanna cookie\n
every second
My environment:
$ uname -a
Linux pop-os 5.17.5-76051705-generic #202204271406~1651504840~22.04~63e51bd SMP PREEMPT Mon May 2 15: x86_64 x86_64 x86_64 GNU/Linux
$ ldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3) 2.35
$ rustc --version
rustc 1.60.0 (7737e0b5c 2022-04-04)
The difference between them lies in the fact that print!("{}", std::str::from_utf8(&buf).unwrap());
will flush the whole buf
to stdout, whereas write(1, buf, read_len);
will just write read_len
bytes.
If we change write(1, buf, read_len);
to write(1, buf, 100);
in C or use stdout().write(&buf[0..n]).unwrap();
in Rust they have the same behavior.