match 和模式匹配深入
C 程序员第一次看到 match,很容易把它当成更严格的 switch。
这个类比能帮你入门,但不够。C 的 switch 主要按整数值分支;如果状态背后还有数据,你还要手工从结构体或 union 里取字段。
C 程序员第一次看到 match,很容易把它当成更严格的 switch。
这个类比能帮你入门,但不够。C 的 switch 主要按整数值分支;如果状态背后还有数据,你还要手工从结构体或 union 里取字段。
前面讲过 Drop 的第一层作用:值离开作用域时自动释放资源。
这已经能替代很多 C 里的 goto cleanup。但真正在库代码里包装文件句柄、连接、锁、临时目录或 FFI handle 时,只知道“离开作用域会释放”还不够。
C 里,拿到 T * 通常意味着“可以改”。问题是,C 不阻止你同时留下很多入口:
uint8_t *a = buf;
uint8_t *b = buf;
a[0] = 1;
printf("%u\n", b[0]);
这段代码本身没错,但在真实项目里,多个指针同时读写同一块数据,会让状态变化很难追踪。谁刚刚改过?谁还拿着旧视图?某个函数会不会在你读之前顺手改了?
阅读更多C 程序员看赋值和传参,第一反应通常是“复制一份值”:
uint32_t a = 10;
uint32_t b = a;
结构体也可以按值复制:
struct Point p1 = {1, 2};
struct Point p2 = p1;
但一旦结构体里有指针,事情就变复杂:
struct Buffer {
uint8_t *data;
size_t len;
};
struct Buffer b2 = b1; // 只是复制指针和长度
这不是深拷贝。b1.data 和 b2.data 指向同一块内存。谁释放?能不能同时改?释放一个以后另一个还能不能用?C 里这些问题靠约定处理。
C 项目里测试常从一个额外的 main 开始:
#include <assert.h>
int main(void)
{
assert(checksum((uint8_t[]){1, 2, 3}, 3) == 6);
return 0;
}
或者写几个小程序、shell 脚本、Makefile 目标:
test:
./test_checksum
./test_parser
Rust 把这个入口直接放进 Cargo:
cargo test
第一层可以先记住:Rust 的测试不是另外一套工程系统,而是 crate 的一部分。
阅读更多命令行工具的输出不是随便打印几行文本。对 shell、CI 和其他程序来说,它的接口通常是三件事:
stdout -> 正常数据
stderr -> 诊断信息、错误信息
exit code -> 成功或失败
C 里通常写:
阅读更多C 库 API 里,一个指针能表达很多事:
int parse_packet(const uint8_t *data, size_t len, struct Packet *out);
void normalize(uint8_t *data, size_t len);
void consume_buffer(uint8_t *data, size_t len);
uint8_t *make_packet(size_t *out_len);
这些签名看起来都和指针有关,但 API 语义完全不同:
Rust 库 API 的第一层设计,就是把这些意图写进类型。
阅读更多C 里动态分配通常从这几个函数开始:
void *p = malloc(size);
p = realloc(p, new_size);
free(p);
Rust 普通代码很少直接调用分配器。更常见的是用拥有类型表达“这块内存归谁管”:
Box<T> -> 堆上的一个 T
Vec<T> -> 堆上的连续可增长数组
String -> 堆上的可增长 UTF-8 文本
这些类型内部当然会分配和释放内存,但调用方通常不写 free。值离开作用域时,释放由类型的 Drop 完成。
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 里位标志很常见:
#define FLAG_READ (1u << 0)
#define FLAG_WRITE (1u << 1)
#define FLAG_ERROR (1u << 2)
uint32_t flags = FLAG_READ | FLAG_WRITE;
if (flags & FLAG_ERROR) {
handle_error();
}
Rust 当然也能做位运算。但有两个习惯要先改:
C: if (flags & FLAG_ERROR)
Rust: if flags & FLAG_ERROR != 0
Rust 的 if 条件必须是 bool。整数不会自动当作真假值。