类型别名和 newtype
C 里 typedef 很常见:
typedef uint32_t DeviceId;
typedef uint8_t Byte;
typedef int (*compare_fn)(const void *a, const void *b);
它能减少重复、隐藏复杂声明、给一个类型加上业务名字。但 typedef 不一定带来新的类型边界。很多时候它只是给原类型取了另一个名字。
Rust 里对应有两种常用写法:
阅读更多C 里 typedef 很常见:
typedef uint32_t DeviceId;
typedef uint8_t Byte;
typedef int (*compare_fn)(const void *a, const void *b);
它能减少重复、隐藏复杂声明、给一个类型加上业务名字。但 typedef 不一定带来新的类型边界。很多时候它只是给原类型取了另一个名字。
Rust 里对应有两种常用写法:
阅读更多C 程序员看 struct,通常会自然想到内存布局:
#include <stdint.h>
struct Header {
uint8_t version;
uint32_t length;
uint16_t flags;
};
字段按声明顺序摆放,中间可能插入 padding,结构体整体也有对齐要求。这个直觉在写协议、文件格式、驱动寄存器映射和 FFI 时很重要。
阅读更多C 里的 T * 太常用了。它可以表示很多不同语义:
void print_config(const struct Config *cfg);
void reset_config(struct Config *cfg);
int parse_packet(const uint8_t *data, size_t len, struct Packet *out);
void driver_write(void *addr, uint32_t value);
这些在 C 里都长得像指针,但语义完全不同:
Rust 不会把这些都塞进一个“指针”概念里。第一层可以先用这张表:
阅读更多C 项目常用 .h 和 .c 分工:
packet.h -> 对外声明结构体、函数、宏
packet.c -> 实现函数,隐藏内部 helper
main.c -> include packet.h 后调用公开函数
例如:
/* packet.h */
int packet_parse(const uint8_t *data, size_t len, struct Packet *out);
/* packet.c */
static int parse_header(const uint8_t *data, size_t len);
int packet_parse(const uint8_t *data, size_t len, struct Packet *out)
{
/* ... */
}
Rust 也要区分文件、内部实现和公开 API,但模型不是 #include。第一层可以这样看:
C 程序员理解作用域,通常先想到名字可见性:
void handle(void)
{
int offset = 0;
{
int len = 16;
offset += len;
}
/* len 不可见,offset 仍然可见 */
}
Rust 的块作用域也有这个规则:
fn handle() {
let mut offset = 0;
{
let len = 16;
offset += len;
}
// len 不可见,offset 仍然可见
}
但 Rust 的作用域不只决定名字能不能用。它还会影响资源什么时候释放、绑定能不能被遮蔽、引用能不能继续存在,以及哪些名字能成为模块外的 API。
阅读更多C 程序员看 Rust 函数,第一眼会觉得只是语法换了位置:
int add(int a, int b)
{
return a + b;
}
Rust 写成:
fn add(a: i32, b: i32) -> i32 {
a + b
}
这两个函数做的是同一件事,但 Rust 函数有几个早期就要适应的习惯:
-> 后return 更多用于早返回()Result这些都不是所有权的完整理论,但会直接影响你怎么写第一个库函数。
阅读更多C 程序员看 Rust 控制流,最容易先找这些对应关系:
C if -> Rust if
C while -> Rust while
C for -> Rust for
C break -> Rust break
C continue -> Rust continue
这些对应关系大体成立,但有几个关键差异会影响日常代码:
阅读更多C 程序员看 Rust 运算符,最容易误判的不是 +、-、*、/ 长得像不像,而是 C 里很多自动发生的转换,在 Rust 里不会自动发生。
C 代码里常见这种写法:
#include <stdint.h>
#include <stddef.h>
uint8_t version = 1;
size_t len = 128;
uint32_t total = version + len;
if (total) {
/* ... */
}
Rust 会要求你把类型关系写清楚:
阅读更多C 程序员进入 Rust,最先要过的不是所有权,而是能不能顺手写出普通变量、常量和基本类型。
C 里常见代码大概是这样:
#include <stdint.h>
#include <stdbool.h>
#define MAX_PACKET_SIZE 1024
int retry = 3;
uint32_t flags = 0;
bool enabled = true;
char tag = 'A';
Rust 也能表达这些东西,但默认习惯不完全一样:
阅读更多Rust 调 C,表面上只是多写一段 extern "C" 声明。
真正需要处理的是另一件事:C 函数没有 Rust 的借用、生命周期、Result 和析构规则。Rust 可以调用它,但编译器无法证明这个调用是否满足 C API 的前提。