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变体绑定数据,编译期一致
内存管理手动嵌套freeDrop自动递归
数组成员JsonValue** + countVec
对象成员JsonMember* + countVec<(String, JsonValue)>

二、架构映射

C层次:         Data Model → Lexer → Parser → Public API
Rust对应:      enum      → &str迭代  → 递归+Result → json_parse/json_stringify
层次C实现方式Rust对应
数据模型enum + unionenum with data
内存管理calloc/freeBox/Vec + Drop
词法分析指针移动 + 字符比较&str切片 + chars()迭代器
解析器递归下降 + goto递归函数 + Result + ?
序列化snprintf + strdup + mallocformat! + 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而非’\0’哨兵值;正确处理UTF-8多字节字符。

公共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穷举检查。

五、渐进重构策略

对于更大项目:

  1. 外观层:保留C,Rust写高层API通过FFI调用(C代码极复杂且经验证)
  2. 夹心层:外层Rust安全API + 中层C核心算法 + 内层Rust数据结构
  3. 全面重写:c2rust转译 → 逐步消除unsafe(<5000行)
  4. 模块化迁移:逐个C模块替换,保持C ABI不变(>10000行)

六、C vs Rust 对比总结

指标C版本Rust版本
代码行数~280行~220行
unsafe块100%(全是不安全)0
空指针风险每次访问都可能不存在
内存泄漏风险调用者忘记free编译期防止
错误信息所有错误返回NULL精确位置+原因
测试覆盖需手动编写#[test]内置

循环测试验证正确性:json_stringify(json_parse(input)) 应与输入等价。

相关链接