C项目结构重构
一、案例项目:MiniJSON解析器
以轻量级JSON解析库作为重构目标,覆盖分词、解析、序列化全流程。C版本约280行,Rust版本约220行。
C数据模型(标记联合体)
typedef enum { JSON_NULL, JSON_BOOL, JSON_NUMBER, JSON_STRING, JSON_ARRAY, JSON_OBJECT } JsonType;
typedef struct JsonValue {
JsonType type;
union {
int boolean; double number; char* string;
struct { JsonValue** items; size_t count; } array;
struct { JsonMember* members; size_t count; } object;
} data;
} JsonValue;问题:type和data分离可能不一致;union不跟踪活跃字段(UB);char*无所有权追踪。
Rust数据模型
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null, Bool(bool), Number(f64),
String(String), Array(Vec<JsonValue>), Object(Vec<(String, JsonValue)>),
}| 方面 | C实现 | Rust实现 |
|---|---|---|
| 类型安全 | 标记和数据分离 | enum变体绑定数据,编译期一致 |
| 内存管理 | 手动嵌套free | Drop自动递归 |
| 数组成员 | JsonValue** + count | Vec |
| 对象成员 | JsonMember* + count | Vec<(String, JsonValue)> |
二、架构映射
C层次: Data Model → Lexer → Parser → Public API
Rust对应: enum → &str迭代 → 递归+Result → json_parse/json_stringify
| 层次 | C实现方式 | Rust对应 |
|---|---|---|
| 数据模型 | enum + union | enum with data |
| 内存管理 | calloc/free | Box/Vec + Drop |
| 词法分析 | 指针移动 + 字符比较 | &str切片 + chars()迭代器 |
| 解析器 | 递归下降 + goto | 递归函数 + Result + ? |
| 序列化 | snprintf + strdup + malloc | format! + String |
三、错误类型设计
#[derive(Debug)]
pub enum ParseError {
UnexpectedEof, UnexpectedChar { pos: usize, expected: String, got: char },
InvalidNumber { pos: usize }, InvalidEscape { pos: usize, char: char },
UnclosedString, TrailingData(usize),
}相比于C返回NULL(丢失一切错误信息),Rust携带精确位置和原因。
四、逐函数重构
词法分析器
C版本:
typedef struct { const char* input; size_t pos, len; } Lexer;
static char lexer_peek(const Lexer* lex) {
return (lex->pos < lex->len) ? lex->input[lex->pos] : '\0';
}Rust版本:
struct Lexer<'a> { input: &'a str, pos: usize }
impl<'a> Lexer<'a> {
fn peek(&self) -> Option<char> { self.input[self.pos..].chars().next() }
fn next(&mut self) -> Option<char> {
let c = self.peek()?;
self.pos += c.len_utf8();
Some(c)
}
}关键改进:&str自带边界;peek返回Option
公共API
pub fn json_parse(input: &str) -> Result<JsonValue, ParseError> {
let mut lexer = Lexer::new(input);
let value = parse_value(&mut lexer)?;
lexer.skip_whitespace();
if !lexer.is_done() { return Err(ParseError::TrailingData(lexer.pos())); }
Ok(value)
}
pub fn json_stringify(value: &JsonValue) -> String {
match value {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => format!("{}", n),
JsonValue::String(s) => stringify_string(s),
JsonValue::Array(arr) => {
let items: Vec<_> = arr.iter().map(json_stringify).collect();
format!("[{}]", items.join(","))
}
JsonValue::Object(obj) => {
let items: Vec<_> = obj.iter()
.map(|(k, v)| format!("{}:{}", stringify_string(k), json_stringify(v)))
.collect();
format!("{{{}}}", items.join(","))
}
}
}无需json_free函数——Drop trait自动递归释放整棵JSON树。
解析器核心
fn parse_value(lexer: &mut Lexer) -> Result<JsonValue, ParseError> {
lexer.skip_whitespace();
match lexer.peek() {
None => Err(UnexpectedEof),
Some('"') => parse_string(lexer),
Some('[') => parse_array(lexer),
Some('{') => parse_object(lexer),
Some(c) if c == '-' || c.is_ascii_digit() => parse_number(lexer),
Some('t') | Some('f') | Some('n') => parse_keyword(lexer),
Some(c) => Err(UnexpectedChar { pos: lexer.pos(), expected: "value".into(), got: c }),
}
}parse_object 对比
C版本在realloc失败时需要手动goto清理:释放key_val、val、已分配成员、重置members数组(约15行清理代码)。Rust版本只需:
members.push((key, value)); // Vec自动扩容,失败时保持原Vec不变
// 错误时已推入的元素由Vec自动释放便捷访问器
impl JsonValue {
pub fn index(&self, idx: usize) -> Option<&JsonValue> {
match self { JsonValue::Array(arr) => arr.get(idx), _ => None }
}
pub fn key(&self, k: &str) -> Option<&JsonValue> {
match self { JsonValue::Object(m) => m.iter().find(|(key,_)| key==k).map(|(_,v)| v), _ => None }
}
pub fn as_str(&self) -> Option<&str> {
match self { JsonValue::String(s) => Some(s.as_str()), _ => None }
}
pub fn as_f64(&self) -> Option<f64> {
match self { JsonValue::Number(n) => Some(*n), _ => None }
}
}对应C版本的访问器每次都检查 v && v->type == JSON_XXX,Rust通过match穷举检查。
五、渐进重构策略
对于更大项目:
- 外观层:保留C,Rust写高层API通过FFI调用(C代码极复杂且经验证)
- 夹心层:外层Rust安全API + 中层C核心算法 + 内层Rust数据结构
- 全面重写:c2rust转译 → 逐步消除unsafe(<5000行)
- 模块化迁移:逐个C模块替换,保持C ABI不变(>10000行)
六、C vs Rust 对比总结
| 指标 | C版本 | Rust版本 |
|---|---|---|
| 代码行数 | ~280行 | ~220行 |
| unsafe块 | 100%(全是不安全) | 0 |
| 空指针风险 | 每次访问都可能 | 不存在 |
| 内存泄漏风险 | 调用者忘记free | 编译期防止 |
| 错误信息 | 所有错误返回NULL | 精确位置+原因 |
| 测试覆盖 | 需手动编写 | #[test]内置 |
循环测试验证正确性:json_stringify(json_parse(input)) 应与输入等价。