C 程序员写工具或基础库,很快会碰到这些接口:
FILE *fp = fopen(path, "rb");
fread(buf, 1, sizeof(buf), fp);
fclose(fp);
DIR *dir = opendir(path);
system("gzip file.log");
getenv("HOME");
Rust 标准库里对应的入口主要是:
std::fs -> 文件和目录的便捷操作
std::io -> Read / Write / BufRead 等 I/O trait
std::env -> 参数、环境变量、当前目录
std::process -> 启动子进程、取退出状态和输出
这一篇不展开所有权和 Drop 的细节,只看 C 常用库函数在 Rust 里的第一层替代方式。
读整个文件
C 里读文件通常先 fopen,再循环 fread:
FILE *fp = fopen(path, "rb");
if (!fp) {
return -1;
}
size_t n = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
Rust 如果只是读完整文件,可以直接用:
let data = std::fs::read(path)?;
std::fs::read 返回 Result<Vec<u8>, std::io::Error>。成功时得到字节,失败时得到错误。
如果读文本:
let text = std::fs::read_to_string(path)?;
这里会检查内容是不是合法 UTF-8。二进制数据用 read,文本用 read_to_string,不要把所有东西都当成 C 的 char *。
逐块读取用 File 和 Read
大文件或流式处理不要一次读进内存。Rust 里打开文件:
use std::fs::File;
use std::io::{self, Read};
fn checksum(path: &str) -> io::Result<u32> {
let mut file = File::open(path)?;
let mut buf = [0_u8; 4096];
let mut sum = 0_u32;
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break;
}
for byte in &buf[..n] {
sum += *byte as u32;
}
}
Ok(sum)
}
这里的 read 来自 std::io::Read。它和 C 的 read / fread 很像:把数据写进调用方给的 buffer,返回实际读到的字节数。
不同点是:错误走 Result,文件关闭由 File 离开作用域时自动完成。
写文件
C 里:
FILE *fp = fopen(path, "wb");
fwrite(data, 1, len, fp);
fclose(fp);
Rust 写整个文件:
std::fs::write(path, data)?;
如果要逐步写:
use std::fs::File;
use std::io::{self, Write};
fn write_report(path: &str, lines: &[String]) -> io::Result<()> {
let mut file = File::create(path)?;
for line in lines {
writeln!(file, "{line}")?;
}
Ok(())
}
Write 提供 write、write_all、flush 等方法。writeln! 可以写到实现了 Write 的目标上。
缓冲 I/O 用 BufReader 和 BufWriter
C 的 FILE * 本身带缓冲。Rust 里的 File 更接近文件句柄;如果需要按行读或减少系统调用,显式包一层缓冲:
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn print_lines(path: &str) -> io::Result<()> {
let file = File::open(path)?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
println!("{line}");
}
Ok(())
}
写入时:
use std::fs::File;
use std::io::{self, BufWriter, Write};
fn save_lines(path: &str, lines: &[String]) -> io::Result<()> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
for line in lines {
writeln!(writer, "{line}")?;
}
writer.flush()?;
Ok(())
}
第一层可以这样记:需要按行读、频繁小块读写时,用 BufReader / BufWriter。
目录操作
C 里常见 opendir / readdir:
DIR *dir = opendir(path);
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
puts(ent->d_name);
}
closedir(dir);
Rust 遍历目录:
use std::fs;
use std::io;
fn list_dir(path: &str) -> io::Result<()> {
for entry in fs::read_dir(path)? {
let entry = entry?;
println!("{}", entry.path().display());
}
Ok(())
}
注意 read_dir(path)? 只表示目录打开成功。循环里的每个 entry 仍然可能失败,所以还有一个 entry?。
常用目录操作:
std::fs::create_dir("logs")?;
std::fs::create_dir_all("logs/app/2026")?;
std::fs::remove_file("old.log")?;
std::fs::remove_dir("empty-dir")?;
std::fs::remove_dir_all("tree")?;
删除目录树要谨慎,和 C 里递归删除一样,错误路径和目标路径都要明确。
路径用 Path 和 PathBuf
C 代码常把路径当 const char *。Rust 里路径类型是:
std::path::Path
std::path::PathBuf
函数参数可以先写成:
use std::path::Path;
fn load_config(path: &Path) -> std::io::Result<String> {
std::fs::read_to_string(path)
}
调用时:
let text = load_config(Path::new("config.toml"))?;
需要拼路径:
let path = std::path::PathBuf::from("logs").join("app.log");
不要优先用字符串拼接路径。PathBuf::join 更直接,也更能表达“这是文件系统路径”。
环境变量和当前目录
C 里用 getenv:
const char *home = getenv("HOME");
Rust:
let home = std::env::var("HOME");
它返回 Result<String, VarError>。环境变量可能不存在,也可能不是合法 Unicode。
如果要保留操作系统原始字符串语义,用:
let path = std::env::var_os("PATH");
当前目录:
let cwd = std::env::current_dir()?;
std::env::set_current_dir("/tmp")?;
命令行参数在第一篇里已经用过:
let args: Vec<String> = std::env::args().collect();
如果参数可能包含非 Unicode 路径,后续可以用 args_os()。
启动子进程
C 里最简单会用 system:
int rc = system("gzip file.log");
Rust 不要默认拼 shell 字符串。用 std::process::Command:
use std::process::Command;
let status = Command::new("gzip")
.arg("file.log")
.status()?;
if !status.success() {
eprintln!("gzip failed: {status}");
}
如果要拿 stdout:
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()?;
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
println!("{text}");
}
Command::new("prog").arg(value) 比拼接 "prog value" 更安全,也更接近 execve 的参数数组模型。
这一层先记住什么
C 常用库函数到 Rust 标准库的第一层对应关系:
fopen/fread whole file -> std::fs::read / read_to_string
fread loop -> File + Read
fwrite -> File + Write / std::fs::write
FILE * buffering -> BufReader / BufWriter
opendir/readdir -> std::fs::read_dir
getenv -> std::env::var / var_os
cwd -> std::env::current_dir / set_current_dir
system -> std::process::Command
return code / errno -> Result / io::Error / ExitStatus
Rust 标准库没有把这些能力藏起来,只是把边界拆得更明确:文件内容是字节还是文本,路径是字符串还是 Path,错误是返回值还是进程退出状态,子进程参数是数组还是 shell 命令行。