跳转到主内容控制流:if、while、loop 和 for | 物联网民工

控制流:if、while、loop 和 for

C 程序员看 Rust 控制流,最容易先找这些对应关系:

C if      -> Rust if
C while   -> Rust while
C for     -> Rust for
C break   -> Rust break
C continue -> Rust continue

这些对应关系大体成立,但有几个关键差异会影响日常代码:

  • if 和 while 的条件必须是 bool
  • if 可以产生值
  • Rust 没有 C 的三段式 for
  • Rust 的 for 是遍历一个可迭代对象
  • loop 表示明确的无限循环,也可以用 break value 返回值

这些差异不是高级概念,写第一个 CLI 或处理 buffer 时马上会遇到。

if 条件必须是 bool

C 里 if 接受整数、指针和表达式结果:

if (len) {
    handle_data();
}

if (ptr) {
    use_ptr(ptr);
}

if (flags & 0x01) {
    enable();
}

Rust 的 if 条件必须是 bool:

if len != 0 {
    handle_data();
}

if !ptr.is_null() {
    // raw pointer case
}

if flags & 0x01 != 0 {
    enable();
}

这和运算符那篇是同一条规则:Rust 不把整数隐式当作真假。条件里要写清楚判断标准。

在普通 Rust 代码里,更常见的是直接用 API 表达意图:

if !data.is_empty() {
    handle_data(data);
}

if name.is_empty() {
    return Err(ConfigError::EmptyName);
}

这比 if (len) 更啰嗦一点,但读者不用猜“非零”背后到底代表什么状态。

if 可以产生值

C 里经常用三目运算符:

int limit = debug ? 64 : 1024;

Rust 没有 ?:。if 本身可以产生值:

let limit = if debug { 64 } else { 1024 };

两个分支必须产生同一种类型:

let limit = if debug { 64 } else { "large" }; // 编译错误

这也适合写简单的选择逻辑:

let mode = if readonly {
    "read"
} else {
    "write"
};

如果只是执行动作,不需要返回值,写法和 C 很接近:

if verbose {
    eprintln!("reading config");
}

区别在于:Rust 的 if 既能当控制流,也能当表达式。是否产生值,取决于你怎么使用它。

while 接近 C,但条件仍然是 bool

C 的 while:

while (offset < len) {
    offset++;
}

Rust 写法很接近:

while offset < len {
    offset += 1;
}

同样,条件必须是 bool。C 里的这类写法:

while (len--) {
    consume();
}

不适合直接搬到 Rust。更清楚的写法是:

while len > 0 {
    consume();
    len -= 1;
}

处理读取循环时,也常把结果先绑定出来:

while offset < data.len() {
    let byte = data[offset];
    offset += 1;
    handle_byte(byte);
}

这仍然是 C 程序员熟悉的循环模型,只是 Rust 不鼓励把递减、赋值和条件压到同一个表达式里。

loop 表示明确的无限循环

C 里无限循环常写:

for (;;) {
    poll();
}

或:

while (1) {
    poll();
}

Rust 用 loop:

loop {
    poll();
}

这比 while true 更直接:这里就是无限循环,直到内部 break、return 或错误传播。

loop 还可以通过 break 返回值:

let packet = loop {
    if let Some(packet) = try_read_packet()? {
        break packet;
    }
};

这类写法适合“不断尝试,直到拿到一个结果”。C 里通常要先声明一个变量,再在循环里赋值并跳出;Rust 可以让循环本身产生结果。

Rust 的 for 不是 C 的三段式循环

C 的 for 最常见是三段式:

for (size_t i = 0; i < len; i++) {
    sum += data[i];
}

Rust 没有这种 for (init; condition; step)。Rust 的 for 遍历一个可迭代对象:

for byte in data {
    sum += *byte as u32;
}

如果需要下标,用范围:

for i in 0..data.len() {
    sum += data[i] as u32;
}

0..data.len() 不包含右边界,类似 C 里的 i < len。

如果同时需要下标和值,用 enumerate:

for (i, byte) in data.iter().enumerate() {
    println!("{i}: {byte}");
}

Rust 的默认倾向是:能直接遍历数据,就少写手工下标。下标不是不能用,但不应该成为所有循环的默认形状。

范围要分清是否包含右边界

Rust 常用两种范围:

0..len    // 不包含 len
0..=last  // 包含 last

对应 C 里的两种判断:

for (i = 0; i < len; i++)   // 0..len
for (i = 0; i <= last; i++) // 0..=last

遍历 slice 时,最常见的是不包含右边界:

for i in 0..data.len() {
    handle(data[i]);
}

但如果只是读每个元素,优先写:

for byte in data {
    handle(*byte);
}

这能少一类越界错误,也更接近 Rust 对 slice 的使用方式。

break 和 continue 基本熟悉

C 的 break 和 continue:

for (size_t i = 0; i < len; i++) {
    if (data[i] == 0) {
        break;
    }
    if (data[i] == 0xff) {
        continue;
    }
    handle(data[i]);
}

Rust 写法类似:

for byte in data {
    if *byte == 0 {
        break;
    }
    if *byte == 0xff {
        continue;
    }
    handle(*byte);
}

嵌套循环里,Rust 可以用标签指定跳出哪一层:

'outer: for row in rows {
    for byte in row {
        if *byte == 0 {
            break 'outer;
        }
    }
}

这比在 C 里用额外 flag 或 goto 跳出多层循环更明确。

match 不是 switch 的简单换皮

C 里多分支常用 switch:

switch (state) {
case 0:
    init();
    break;
case 1:
    run();
    break;
default:
    fail();
    break;
}

Rust 有 match:

match state {
    0 => init(),
    1 => run(),
    _ => fail(),
}

第一层可以先把它当作更严格的多分支。它不会自动 fallthrough,每个分支用 =>,_ 表示默认分支。

遇到 enum 以后,match 会变得更重要:它不只是替代 switch,还会检查枚举状态是否覆盖完整。

最小迁移表

C if (x)              -> Rust if x != 0 / !is_empty() / !is_null()
C ?:                  -> Rust if ... { ... } else { ... }
C while (cond)        -> Rust while cond
C while (len--)       -> Rust while len > 0 { len -= 1; ... }
C for (;;)            -> Rust loop
C for (i=0; i<len; i++) -> Rust for i in 0..len
C 遍历数组             -> Rust for item in slice
C break / continue    -> Rust break / continue
C 多层跳出             -> Rust loop label + break 'label
C switch              -> Rust match

Rust 控制流不是难在语法,而是默认减少了 C 里“把赋值、递增、条件、隐式真假压到一个表达式里”的写法。条件写清楚,遍历对象写清楚,后面的借用和所有权才更容易理解。

继续阅读