Rust

43 文章

类型别名和 newtype

4 分钟

C 里 typedef 很常见:

typedef uint32_t DeviceId;
typedef uint8_t Byte;
typedef int (*compare_fn)(const void *a, const void *b);

它能减少重复、隐藏复杂声明、给一个类型加上业务名字。但 typedef 不一定带来新的类型边界。很多时候它只是给原类型取了另一个名字。

Rust 里对应有两种常用写法:

阅读更多

结构体和内存布局

5 分钟

C 程序员看 struct,通常会自然想到内存布局:

#include <stdint.h>

struct Header {
    uint8_t version;
    uint32_t length;
    uint16_t flags;
};

字段按声明顺序摆放,中间可能插入 padding,结构体整体也有对齐要求。这个直觉在写协议、文件格式、驱动寄存器映射和 FFI 时很重要。

阅读更多

指针第一层:引用和裸指针怎么分工

4 分钟

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 里都长得像指针,但语义完全不同:

  • 只读借用一个对象
  • 修改调用方对象
  • 指针加长度表示一段 buffer
  • 输出参数
  • 可能为空的指针
  • 硬件地址或 FFI 传来的地址

Rust 不会把这些都塞进一个“指针”概念里。第一层可以先用这张表:

阅读更多

模块和文件组织:从 .h/.c 到 mod

4 分钟

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。第一层可以这样看:

阅读更多

作用域、绑定和名字可见性

4 分钟

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。

阅读更多

函数、参数和返回值

4 分钟

C 程序员看 Rust 函数,第一眼会觉得只是语法换了位置:

int add(int a, int b)
{
    return a + b;
}

Rust 写成:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

这两个函数做的是同一件事,但 Rust 函数有几个早期就要适应的习惯:

  • 参数名在前,类型在后
  • 返回类型写在 -> 后
  • 函数末尾表达式可以作为返回值
  • return 更多用于早返回
  • 没有返回值的函数返回 ()
  • C 的输出参数和返回码,在 Rust 里通常会变成返回值和 Result

这些都不是所有权的完整理论,但会直接影响你怎么写第一个库函数。

阅读更多

运算符、表达式和类型转换

4 分钟

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 会要求你把类型关系写清楚:

阅读更多

基本类型、变量和常量

4 分钟

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 也能表达这些东西,但默认习惯不完全一样:

阅读更多

FFI:让 Rust 调用 C

4 分钟

Rust 调 C,表面上只是多写一段 extern "C" 声明。

真正需要处理的是另一件事:C 函数没有 Rust 的借用、生命周期、Result 和析构规则。Rust 可以调用它,但编译器无法证明这个调用是否满足 C API 的前提。

阅读更多