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 里只读 buffer:
uint32_t checksum(const uint8_t *data, size_t len);
Rust 写成 slice:
fn checksum(data: &[u8]) -> u32 {
data.iter().map(|b| *b as u32).sum()
}
&[u8] 表示函数临时读取一段字节,不接管内存,也不能修改。
文本参数同理。不要写:
fn print_name(name: String) {
println!("{name}");
}
如果函数只是读取,写:
fn print_name(name: &str) {
println!("{name}");
}
这样调用方可以传字符串字面量,也可以传 String 的借用:
print_name("sensor");
let name = String::from("device");
print_name(&name);
第一条规则:
函数只是读:优先接收 &T / &[T] / &str
原地修改:可变借用
C 里修改调用方 buffer:
void xor_in_place(uint8_t *data, size_t len, uint8_t key);
Rust 写:
fn xor_in_place(data: &mut [u8], key: u8) {
for byte in data {
*byte ^= key;
}
}
调用方必须明确给可变借用:
let mut data = vec![1_u8, 2, 3];
xor_in_place(&mut data, 0xAA);
如果修改的是一个结构体:
fn reset_config(config: &mut Config) {
config.retry = 0;
}
第二条规则:
函数要原地修改调用方数据:接收 &mut T / &mut [T]
创建新数据:返回拥有类型
C 里常见“返回新分配内存 + out_len”:
uint8_t *encode_packet(const uint8_t *payload, size_t len, size_t *out_len);
Rust 普通库 API 不需要这样设计。直接返回拥有类型:
fn encode_packet(payload: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(payload.len() + 2);
out.push(0xAA);
out.extend_from_slice(payload);
out.push(0x55);
out
}
如果可能失败:
fn encode_packet(payload: &[u8]) -> Result<Vec<u8>, EncodeError> {
if payload.len() > u8::MAX as usize {
return Err(EncodeError::TooLarge);
}
let mut out = Vec::with_capacity(payload.len() + 2);
out.push(0xAA);
out.extend_from_slice(payload);
out.push(0x55);
Ok(out)
}
文本同理:
fn make_topic(device: &str) -> String {
format!("devices/{device}/status")
}
第三条规则:
函数创建新数据:返回 Vec<T> / String / Box<T> / 自定义拥有类型
输出参数:能返回就返回
C 里输出参数很常见:
int parse_header(const uint8_t *data, size_t len, struct Header *out);
Rust 可以直接返回:
fn parse_header(data: &[u8]) -> Result<Header, ParseError> {
if data.len() < 4 {
return Err(ParseError::TooShort);
}
Ok(Header {
version: data[0],
flags: data[1],
len: u16::from_be_bytes([data[2], data[3]]),
})
}
如果要返回多个值,用 tuple 或结构体:
fn parse_field(data: &[u8]) -> Result<(Field, usize), ParseError> {
let field = parse_one_field(data)?;
Ok((field, field.encoded_len()))
}
只有在确实要复用调用方提供的 buffer、避免分配或贴近底层接口时,再用 &mut 输出:
fn encode_into(payload: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
if out.len() < payload.len() + 2 {
return Err(EncodeError::TooSmall);
}
out[0] = 0xAA;
out[1..1 + payload.len()].copy_from_slice(payload);
out[1 + payload.len()] = 0x55;
Ok(payload.len() + 2)
}
第四条规则:
C 输出参数:Rust 优先返回值;必须复用调用方内存时才用 &mut
接管资源:按值接收
C 里有些 API 会接管调用方传来的资源:
void queue_take_packet(uint8_t *packet, size_t len);
调用后调用方不能再释放或使用这块内存。Rust 里这种 API 应该按值接收拥有类型:
fn queue_take_packet(packet: Vec<u8>) {
println!("queued {}", packet.len());
}
调用方:
let packet = vec![1_u8, 2, 3];
queue_take_packet(packet);
// packet 不能再用
如果函数只是看一眼,不要接收 Vec<u8>:
fn inspect_packet(packet: &[u8]) {
println!("len = {}", packet.len());
}
第五条规则:
函数要接管资源:按值接收 Vec<T> / String / File / 自定义拥有类型
函数只是使用资源:接收借用
结构体字段也要按语义选
API 不只体现在函数参数上,也体现在结构体字段上。
如果结构体要拥有配置文本:
struct Config {
name: String,
}
如果只是临时查看外部数据,通常不该把引用存进长寿命结构体,除非你明确要引入生命周期参数:
struct View<'a> {
data: &'a [u8],
}
第一层库 API 里,如果不想把生命周期暴露给调用方,常常让结构体拥有数据:
struct Packet {
data: Vec<u8>,
}
这不是说引用字段不能用,而是它会把“这个结构体不能比被借用的数据活得更久”变成 API 的一部分。初学阶段不要随手把所有字段都写成引用。
一张决策表
设计 Rust 库 API 时,可以先按这个顺序问:
1. 函数只是读取吗?
-> &T / &[T] / &str
2. 函数要原地修改吗?
-> &mut T / &mut [T]
3. 函数要创建新数据给调用方吗?
-> 返回 Vec<T> / String / Box<T> / 自定义类型
4. 函数要接管调用方资源吗?
-> 按值接收 Vec<T> / String / File / 自定义拥有类型
5. 函数要复用调用方 buffer 避免分配吗?
-> 输入 &[T],输出 &mut [T],返回写入长度或 Result<usize, E>
这比先纠结“Rust 到底要不要所有权”更实用。API 的问题不是语法,而是资源关系。
这一层先记住什么
C 到 Rust 的 API 迁移第一层:
const T * -> &T
const T * + len -> &[T]
T * + len 原地修改 -> &mut [T]
返回 malloc 指针 -> 返回 Vec<T> / String / Box<T>
out 参数 -> 优先返回值 / Result<T, E>
接管调用方资源 -> 按值接收拥有类型
只是临时使用资源 -> 接收借用
好的 Rust API 不会把所有东西都写成 Vec、String 或裸指针。它会让函数签名直接说明:我只是读、我要改、我要创建、还是我要接管。