You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Rust惯用写法改写bzip2解压函数中的C goto与switch逻辑

将带goto与switch的C代码转换为Rust惯用风格

我正在从C语言转向Rust,需要把包含goto和switch(带贯穿执行)的C代码模式改写成符合Rust惯用实践的代码,同时保证功能一致。以下是从bzip2的*.i预处理文件中提取的简化C示例:

#include <stdio.h>
int main() {
    int state = 15;
    int dummy = 0;
    switch (state) {
        case 15:
            dummy = 15; // 在特定条件下设置dummy为15
            if (dummy == 15) goto endhdr_2;
            /* fallthrough */

        case 16:
            dummy = 16;
            printf("dummy");
            /* fallthrough */

        endhdr_2:
        case 42:
            dummy = 42;
            printf("dummy: %d", 42);
            /* fallthrough */

        case 43:
            dummy = 43;
            printf("dummy: %d", 43);
            /* fallthrough */
        
        default:
            break;
    }
    return 0;
}

我知道Rust没有goto,且match和C的switch行为差异很大,请问该如何转换这种模式?


补充上下文

上述简化示例来自经典C程序bzip2的BZ2_decompress函数(位于decompress.c),以下是预处理后decompress.i文件中的实际代码片段:

使用goto的位置

case 14: s->state = 14; while (((Bool)1)) { if (s->bsLive >= 8) { UInt32 v; v = (s->bsBuff >> (s->bsLive-8)) & ((1 << 8)-1); s->bsLive -= 8; uc = v; break; } if (s->strm->avail_in == 0) { retVal = 0; goto save_state_and_return; };; s->bsBuff = (s->bsBuff << 8) | ((UInt32) (*((UChar*)(s->strm->next_in)))); s->bsLive += 8; s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; };

if (uc == 0x17) goto endhdr_2;

endhdr_2:之后的代码

endhdr_2:

      case 42: s->state = 42; while (((Bool)1)) { if (s->bsLive >= 8) { UInt32 v; v = (s->bsBuff >> (s->bsLive-8)) & ((1 << 8)-1); s->bsLive -= 8; uc = v; break; } if (s->strm->avail_in == 0) { retVal = 0; goto save_state_and_return; };; s->bsBuff = (s->bsBuff << 8) | ((UInt32) (*((UChar*)(s->strm->next_in)))); s->bsLive += 8; s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; };
      if (uc != 0x72) { retVal = (-4); goto save_state_and_return; };;
      case 43: s->state = 43; while (((Bool)1)) { if (s->bsLive >= 8) { UInt32 v; v = (s->bsBuff >> (s->bsLive-8)) & ((1 << 8)-1); s->bsLive -= 8; uc = v; break; } if (s->strm->avail_in == 0) { retVal = 0; goto save_state_and_return; };; s->bsBuff = (s->bsBuff << 8) | ((UInt32) (*((UChar*)(s->strm->next_in)))); s->bsLive += 8; s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; };
      if (uc != 0x45) { retVal = (-4); goto save_state_and_return; };;
      case 44: s->state = 44; while (((Bool)1)) { if (s->bsLive >= 8) { UInt32 v; v = (s->bsBuff >> (s->bsLive-8)) & ((1 << 8)-1); s->bsLive -= 8; uc = v; break; } if (s->strm->avail_in == 0) { retVal = 0; goto save_state_and_return; };; s->bsBuff = (s->bsBuff << 8) | ((UInt32) (*((UChar*)(s->strm->next_in)))); s->bsLive += 8; s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; };
      if (uc != 0x38) { retVal = (-4); goto save_state_and_return; };;
      case 45: s->state = 45; while (((Bool)1)) { if (s->bsLive >= 8) { UInt32 v; v = (s->bsBuff >> (s->bsLive-8)) & ((1 << 8)-1); s->bsLive -= 8; uc = v; break; } if (s->strm->avail_in == 0) { retVal = 0; goto save_state_and_return; };; s->bsBuff = (s->bsBuff << 8) | ((UInt32) (*((UChar*)(s->strm->next_in)))); s->bsLive += 8; s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; };
      if (uc != 0x50) { retVal = (-4); goto save_state_and_return; };;
      case 46: s->state = 46; while (((Bool)1)) { if (s->bsLive >= 8) { UInt32 v; v = (s->bsBuff >> (s->bsLive-8)) & ((1 << 8)-1); s->bsLive -= 8; uc = v; break; } if (s->strm->avail_in == 0) { retVal = 0; goto save_state_and_return; };; s->bsBuff = (s->bsBuff << 8) | ((UInt32) (*((UChar*)(s->strm->next_in)))); s->bsLive += 8; s->strm->next_in++; s->strm->avail_in--; s->strm->total_in_lo32++; if (s->strm->total_in_lo32 == 0) s->strm->total_in_hi32++; };
      if (uc != 0x90) { retVal = (-4); goto save_state_and_return; };;

生成与清理decompress.i文件的方法

修改CMakeLists.txt添加add_definitions(-save-temps)即可生成*.i文件,使用以下命令清理文件中的预处理行:

for file in *.i; do awk '!/^#[ 	]*[0-9]+[ 	]+"/' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"; done 

解决方案

针对这类带状态流转、goto跳转和贯穿执行的C代码,Rust中有几种惯用的转换方式,核心是用状态机思想替代C的switch-goto模式:

1. 枚举定义状态+匹配后显式控制流转

先把所有状态定义为枚举,将每个状态的逻辑封装到match分支中,通过返回下一个状态来模拟贯穿或跳转:

#[derive(Debug, Clone, Copy)]
enum State {
    S15,
    S16,
    S42,
    S43,
    Default,
}

fn main() {
    let mut state = State::S15;
    let mut dummy = 0;

    loop {
        state = match state {
            State::S15 => {
                dummy = 15;
                if dummy == 15 {
                    // 模拟goto endhdr_2,直接跳转到S42
                    State::S42
                } else {
                    // 模拟fallthrough到S16
                    State::S16
                }
            }
            State::S16 => {
                dummy = 16;
                println!("dummy");
                // 模拟fallthrough到S42
                State::S42
            }
            State::S42 => {
                dummy = 42;
                println!("dummy: {}", 42);
                // 模拟fallthrough到S43
                State::S43
            }
            State::S43 => {
                dummy = 43;
                println!("dummy: {}", 43);
                // 结束流转
                State::Default
            }
            State::Default => break,
        };
    }
}

这种方式逻辑清晰,每个状态的流转路径一目了然,完全符合Rust的表达式风格。

2. 封装状态处理函数,用返回值控制流转

对于bzip2中更复杂的带循环和提前返回的逻辑,可以把每个状态的处理逻辑封装成独立函数,函数返回下一个状态或终止信号:

// 对应C中的压缩状态结构体
#[derive(Debug)]
struct DecompressState {
    state: u32,
    bs_live: u32,
    bs_buff: u32,
    strm: Stream,
    uc: u8,
}

#[derive(Debug)]
struct Stream {
    avail_in: usize,
    next_in: &'static [u8],
    total_in_lo32: u32,
    total_in_hi32: u32,
}

// 定义处理结果:控制流转方向
enum ProcessResult {
    NextState(u32),
    NeedMoreInput,
    Error(i32),
}

fn process_state_14(s: &mut DecompressState) -> ProcessResult {
    s.state = 14;
    loop {
        if s.bs_live >= 8 {
            let v = (s.bs_buff >> (s.bs_live - 8)) & 0xFF;
            s.bs_live -= 8;
            s.uc = v as u8;
            break;
        }
        if s.strm.avail_in == 0 {
            return ProcessResult::NeedMoreInput;
        }
        // 读取输入字节到bitstream
        let byte = s.strm.next_in[0];
        s.strm.next_in = &s.strm.next_in[1..];
        s.strm.avail_in -= 1;
        s.bs_buff = (s.bs_buff << 8) | (byte as u32);
        s.bs_live += 8;
        s.strm.total_in_lo32 += 1;
        if s.strm.total_in_lo32 == 0 {
            s.strm.total_in_hi32 += 1;
        }
    }
    if s.uc == 0x17 {
        // 模拟goto endhdr_2,跳转到状态42
        ProcessResult::NextState(42)
    } else {
        ProcessResult::NextState(15)
    }
}

fn process_state_42(s: &mut DecompressState) -> ProcessResult {
    s.state = 42;
    loop {
        if s.bs_live >= 8 {
            let v = (s.bs_buff >> (s.bs_live - 8)) & 0xFF;
            s.bs_live -= 8;
            s.uc = v as u8;
            break;
        }
        if s.strm.avail_in == 0 {
            return ProcessResult::NeedMoreInput;
        }
        let byte = s.strm.next_in[0];
        s.strm.next_in = &s.strm.next_in[1..];
        s.strm.avail_in -= 1;
        s.bs_buff = (s.bs_buff << 8) | (byte as u32);
        s.bs_live += 8;
        s.strm.total_in_lo32 += 1;
        if s.strm.total_in_lo32 == 0 {
            s.strm.total_in_hi32 += 1;
        }
    }
    if s.uc != 0x72 {
        ProcessResult::Error(-4)
    } else {
        ProcessResult::NextState(43)
    }
}

// 其他状态处理函数同理...

fn decompress(s: &mut DecompressState) -> i32 {
    loop {
        let result = match s.state {
            14 => process_state_14(s),
            42 => process_state_42(s),
            43 => process_state_43(s),
            // 匹配其他状态...
            _ => break 0,
        };
        match result {
            ProcessResult::NextState(next) => s.state = next,
            ProcessResult::NeedMoreInput => return 0,
            ProcessResult::Error(code) => return code,
        }
    }
    0
}

这种方式将每个状态的逻辑解耦,函数单一职责,通过ProcessResult清晰处理各种跳转和终止情况,避免了C中goto带来的混乱代码结构。

3. 标签块+break模拟局部跳转(简单场景)

对于局部的goto跳转(比如原代码中的save_state_and_return),可以用Rust的标签块配合break来模拟:

fn process_state(s: &mut DecompressState) -> i32 {
    'save_state: loop {
        // 状态处理逻辑...
        if s.strm.avail_in == 0 {
            // 模拟goto save_state_and_return:保存状态后返回
            s.state = 14;
            break 'save_state 0;
        }
        // 其他逻辑...
        if s.uc != 0x72 {
            break 'save_state -4;
        }
        break 'save_state 1; // 正常流转到下一个状态
    }
}

这种方式仅适合局部跳转,不能跨函数或大范围使用,适合替代C中用于提前返回的goto。


内容的提问来源于stack exchange,提问作者Sheldon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:55:55