< 返回版块

lisering 发表于 2026-07-30 23:51

Tags:教程,项目

大家好,我写了一个用 Rust 从零构建 Lisp 解释器的实战教程,希望和大家分享。

项目地址:https://github.com/lisering/lisp-rs


为什么做这个项目

学 Rust 的过程中,最大的痛点是:教程很多,但完整的实战项目很少。写一个解释器是学 Rust 所有权和类型系统的绝佳练习——你会遇到大量真实的借用、克隆、智能指针问题,而且每一步都有明确的正确性验证(测试)。

项目特点

1. 零外部依赖

Cargo.toml 里没有任何依赖:

[package]
name = "lisp-rs"
version = "0.1.0"
edition = "2024"

[dependencies]

连哈希器都是自己手写的 FxHasher(用黄金比例常数做快速搅拌),字符串驻留(string interning)也是从头实现。这意味着学习者可以理解每一行代码,没有任何黑盒。

2. 核心数据结构

整个解释器的核心就是一个 enum + Rc<RefCell>

pub enum LispExp {
    Number(f64),
    Symbol(u64),           // 驻留后的整数 ID,不是 String
    List(Vec<LispExp>),
    Func(fn(&[LispExp]) -> Result<LispExp, LispErr>),
    Lambda(Box<LispLambda>),
    Macro(Box<LispLambda>),
    Bool(bool),
    Nil,
    String(String),
}

pub struct LispLambda {
    pub params: Vec<u64>,
    pub rest: Option<u64>,
    pub body: Box<LispExp>,
    pub env: Rc<RefCell<LispEnv>>,  // 闭包捕获环境
}

如果你正在学 Rust 的 RcRefCellBox,这个项目是一个完整的实战案例。

3. 实现了哪些特性

特性 步骤 Rust 知识点
词法分析 9-11 Vec、迭代器
解析器 12-15 递归、Result 错误传播
变量绑定 16-19 HashMap、自定义 hasher
闭包 37 Rc<RefCell> 共享所有权
尾调用优化 39 循环替代递归(蹦床)
宏系统 71-71e 模式匹配、代码生成
字符串驻留 40 OnceLockRwLock、全局静态

4. TCO 蹦床实现

用循环替代递归,100 万次迭代不会栈溢出:

pub fn eval(exp: &LispExp, env: &mut LispEnv) -> Result<LispExp, LispErr> {
    let mut current_exp = exp.clone();
    let mut current_env = std::mem::take(env);

    loop {
        match &current_exp {
            // ... 自求值类型直接返回
            LispExp::List(elements) => {
                // if 的尾位置优化:不递归,直接改 current_exp 继续 loop
                if *sym == p.if_sym {
                    let cond = eval(&elements[1], &mut current_env)?;
                    current_exp = if is_true { elements[2] } else { elements[3] };
                    continue;  // ← 蹦床:不递归,回到 loop 顶部
                }
            }
        }
    }
}

5. 文档投入

  • 中文 README 约 8,000 行,英文版同样长度
  • 44 张手绘 SVG 架构图(中英双语),覆盖流水线、AST 树、闭包、TCO 蹦床、RC 共享等概念
  • 74 个步骤,每一步先讲要解决什么问题,再写代码
  • 42 个测试用例,随时可以 cargo test 验证

和其他教程的区别

教程 语言 区别
Crafting Interpreters Java/C 它面向有经验程序员,做两遍(tree-walk + bytecode);本教程面向零基础,做一遍 tree-walk
mal (Make a Lisp) 80+语言 它给测试用例但不解释 why;本教程每步先讲为什么
SICP Scheme 它教编程思维,本教程教解释器实现

这不是什么

这是一个教学解释器——tree-walking eval,没有字节码编译器,没有 JIT。优化步骤(40-43)是为了演示这些技术怎么工作,不是为了性能。如果你想要生产级 Scheme,推荐 scheme-rs

项目结构

src/
├── lib.rs         # LispExp enum、LispLambda、Display trait
├── lexer.rs       # 词法分析(字符串 → token)
├── parser.rs      # 语法分析(token → AST)
├── interpreter.rs # 求值器(AST → 结果),约 1050 行
├── env.rs         # 环境(HashMap + FxHasher + Rc<RefCell>)
├── interner.rs    # 字符串驻留(OnceLock + RwLock)
└── main.rs        # REPL 入口

总共约 1,658 行 Rust 代码

欢迎反馈

  • 如果你是 Rust 新手:从步骤 1 开始跟着走,每步都有 cargo test 验证
  • 如果你有经验:直接跳到步骤 37(闭包)和步骤 39(TCO)
  • 欢迎提 issue 和 PR,特别是关于教学讲解的改进建议

评论区

写评论

还没有评论

1 共 0 条评论, 1 页