使用rust编写typescript编译器的难点在什么地方,哪些数据结构是rust不擅长的?
用Rust写TS编译器总体可行(SWC已证明),但确实有特定痛点和“反直觉”的数据结构。下面拆解为“Rust不擅长的数据结构”和“TS编译器架构难点”两个层面:
一、Rust绝对不擅长:带环/共享可变的数据结构
这是编译器最难、最绕不开的坎。TS源码本质是图(AST节点多父引用、符号表循环引用),而Rust所有权是树,直接冲突:
- 双向链接结构:安全Rust写双向链表是本科难题但被称为“噩梦”级。用
Rc<RefCell<...>>能糊但性能差且极易运行时panic(borrow()冲突);改用index(竞技场)或unsafe裸指针才是工程常态。 - 图与任意循环:类型定义互相引用(
A->B, B->A)、带父指针的AST。Rust无GC,学术界都承认这是顽疾,靠纯Rc会内存泄漏,需手动破环或Weak。 - 共享所有权:多个分析阶段共用一个节点并可能修改。C++直接指针,Rust必须套
Rc<RefCell>导致代码里全是.borrow_mut(),臃肿且心智负担重。
二、TS编译器架构的具体难点(不止是数据结构)
- 生命周期与自引用:词法环境需引用自身。若用
&引用,Rust会卡死在自反结构上(无法构造);最终要么全用arena(typed-arena)分配后存索引,要么用pin。 - 增量编译与持久化缓存:TS支持
--watch,难点不是代码逻辑,而是如何把Rust内存结构序列化存盘。图结构反序列化后要恢复完整引用,极难处理(主流方案偏向存计算hash而非存AST)。 - 中间层类型擦除:写通用分析时想存“要么是标识符要么是字面量”,C++用
std::variant,Rust枚举极强但无法向上转型。若定义enum AstNode { Expr(Expr), Stmt(Stmt) },传入具体Expr时无法直接塞进这个枚举,必须手动wrap,大量样板。 - 原地修改/压缩:优化阶段如“常量折叠”,C++习惯覆写节点(变长变短)。Rust借用检查器不允许你持有引用时乱改内存,若强求极省内存则非常别扭。
三、速判:到底能不能忍?
能。SWC证明了Rust写TS解析器+转换器可达“最快之一”。但心态要切换:不要试图在安全Rust里精妙建模“完美的共享图”,该用usize下标时就用,该写unsafe封装内部可变性时就写,用C++思维强套安全Rust只会崩溃。
Rolldown背后的Oxc
Rolldown之所以能“躲开”所有权地狱,正是因为它站在Oxc肩上。Oxc parser + semantic喂给它的是已经算好的、干净的结构,而Oxc semantic自己,正面撞上了你第一轮提的所有雷区。
一、Oxc semantic干了什么?
看文档就明白了:符号表、作用域树、引用跟踪、控制流图。这正是TypeScript编译器最核心的语义分析层,也是Rust所有权模型最讨厌的“带环共享图”。它不是“只解析”,而是真正维护了全程序的变量绑定关系——哪个标识符是声明、哪个是引用、作用域怎么嵌套、闭包捕获了谁。
二、它是怎么在Rust里活下来的?
关键技巧:用索引代替指针,用arena控制生命周期。
Oxc semantic的核心数据结构长这样:SymbolTable就是一个大仓库,里面全是Vec和HashMap;查询不靠引用,靠SymbolId和ReferenceId这两个整数下标。AST节点里存的不是指针,是Cell<Option<ReferenceId>>。ScopeId是u32的包装。
这正是我第一轮说的“该用下标就用下标,该写unsafe就写”的现实范本。他们把图拍平了存进连续数组,所有权归arena统一管理,节点之间只存整数ID。没有共享所有权,只有共享索引。
三、更狠的:连AST设计都在给借用检查器“减负”
Oxc parser明确说:我们不用estree,因为estree节点太模糊。他们自己手写了BindingIdentifier、IdentifierReference、IdentifierName三个完全不同的类型。
这不是为了“规范”,这是为了让类型系统替你保证“不会把表达式节点当声明节点用”。你之前问的“无法向上转型”痛点,Oxc的解法是根本不转——没有泛型父节点,每条路径都是具体的、已知的。编译期就定死。
四、性能压榨到了“作弊”级别
他们为了省一次HashMap扩容,会先遍历一遍AST数清楚每个作用域有几个变量,然后把计数临时塞进scope_id字段(反正第一遍时这个字段是空的),第二遍再用这个数字预分配容量。
这是典型的“Rust工业代码”——不追求完美抽象,能跑、够快就是赢。
五、所以,Oxc是“TS编译器”吗?
不是完全的,但非常接近了。
Oxc semantic能做作用域分析、符号解析、引用跟踪,这是编译器后端的语义模型层。它缺的是真正的类型推导和类型检查——类型兼容性、泛型实例化、条件类型展开这些微软Go版tsc的核心逻辑。
Oxlint现在实现类型感知linting,方式是包装并调用Go版tsc,不是自己重写。这是个极其务实的选择:把自己不擅长的“类型代数”外包,只做自己擅长的“图遍历+模式匹配”。
给你的结论:
你直觉是对的。Oxc确实干了“最复杂”的那部分——所有Rust不擅长的共享图结构,它全扛了,而且用索引+arena+预遍历+unsafe封装,扛得极其漂亮。
它不是“绕过”了问题,是正面击穿了问题。这才是Oxc在Rust生态里真正牛逼的地方。
浙公网安备 33010602011771号