FFI:让 C 调用 Rust
让 C 调用 Rust,不是把 Rust 函数原样暴露给 C。
C 能理解的是 C ABI:符号名、调用约定、整数、指针、结构体布局、谁分配内存、谁释放内存、错误怎么返回。Rust 内部的 String、Vec、Result、trait、panic,都不应该直接跨过 C 边界。
让 C 调用 Rust,不是把 Rust 函数原样暴露给 C。
C 能理解的是 C ABI:符号名、调用约定、整数、指针、结构体布局、谁分配内存、谁释放内存、错误怎么返回。Rust 内部的 String、Vec、Result、trait、panic,都不应该直接跨过 C 边界。
C 开发者第一次看到 unsafe,很容易把它理解成“Rust 里可以写 C 的地方”。
这个理解太粗。
unsafe 不是关闭 Rust 所有检查,也不是把整段代码变回 C。它只允许你做少数编译器无法静态证明安全的操作,例如解引用裸指针、调用 FFI 函数、访问可变全局变量。
C 里写通用代码,常见有三种办法:
void * 加长度和回调这些办法都能工作,但各有代价。void * 会丢类型信息,宏难调试,手写多份容易重复和不一致。
C 里 enum 和 union 经常一起出现。enum 表示当前是哪种类型,union 复用同一块内存保存不同数据:
enum ValueKind {
VALUE_INT,
VALUE_FLOAT,
VALUE_TEXT,
};
struct Value {
enum ValueKind kind;
union {
int i;
float f;
const char *text;
} data;
};
这是 C 里的 tagged union。它很常见,也很有用,但正确性靠纪律:
阅读更多C 开发者第一次看到 lifetime,很容易以为它是在给对象标注“活多久”。
这个理解不准确。
Rust 的 lifetime 不是运行时计时器,也不是对象真实存活时间的手写说明。它更像编译器用来检查引用关系的一套静态约束:一个引用不能比它指向的数据活得更久。
阅读更多上一篇讲所有权:谁拥有,谁释放。
但大多数函数并不想接管资源。它只是想临时看一眼数据,或者临时改一下数据。C 里通常传指针:
void inspect(const uint8_t *buf, size_t len);
void update(uint8_t *buf, size_t len);
Rust 里对应的是借用:
fn inspect(buf: &[u8]) {}
fn update(buf: &mut [u8]) {}
借用的核心不是“Rust 指针语法”,而是访问权限:不拿走所有权,只临时读或写。
阅读更多C 代码里早就有所有权。
只要一段内存需要 free,一个文件需要 fclose,一个句柄需要释放,就一定存在一个问题:谁负责最后把它释放掉。
C 的问题不是没有所有权,而是所有权通常不在类型里。它靠函数名、注释、调用约定和团队经验维护。Rust 的 ownership 做的事,就是把这些约定变成编译器能检查的规则。
阅读更多C 代码里,资源释放通常是靠人维护的。
打开文件要 fclose,申请内存要 free,拿到锁要解锁,初始化了对象就要在每条错误路径里销毁。函数短时还好,资源一多,最常见的结构就是 goto cleanup。
C 开发者很熟悉这种错误处理方式:
int read_config(const char *path, struct Config *out);
函数返回 0 表示成功,非 0 表示失败;真正的结果通过输出参数写回。系统调用还会用 -1 加 errno。资源多一点时,再配合 goto cleanup 收尾。