C 和 Rust 代码片段之间的不同行为

Different behaviors between C and Rust code snippets

我有一个 C 代码片段,它使用 pipe()fork() 在父进程和子进程之间进行通信。我想在 Rust 中复制它。 C中使用的POSIX API,如果Rust标准库中有对应的,优先考虑。

但是,这两个代码片段具有不同的行为。这些不同行为的根本原因是什么?

C 代码片段
// 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] = {'[=10=]'};
    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;
}
Rust 代码片段
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());
            },
        },
        _ => (),
    }
}

预期的行为类似于:

$ 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`
...

但是,对于 Rust 代码片段,我得到如下信息:

$ 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`
...

它每秒打印 Parent: testing...\nChild: I wanna cookie\n

我的环境:

$ 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)

它们之间的区别在于 print!("{}", std::str::from_utf8(&buf).unwrap()); 会将整个 buf 刷新到标准输出,而 write(1, buf, read_len); 只会写入 read_len 个字节。

如果我们在 C 中将 write(1, buf, read_len); 更改为 write(1, buf, 100); 或在 Rust 中使用 stdout().write(&buf[0..n]).unwrap(); 它们具有相同的行为。