如何用Rust惯用写法改写bzip2解压函数中的C goto与switch逻辑
我正在从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

