本文是对 2025 Recap: so many projects 的整理与翻译。内容结构概览2025 年为什么需要复盘这一年项目太多必须整理思路。facet 的起点不满 serde 生态的编译成本尝试用 Rust 反射解决序列化问题。facet 的第一阶段Facettrait、SHAPE、Peek、部分初始化、Miri、unsafe 边界。facet 的挫败运行更慢、编译更慢、二进制更大最初目标没有达成。facet 的重新定位不再只追求“小而快”转向更好的开发体验和错误报告。facet-format 与格式生态统一JSON、YAML、TOML、Postcard、MsgPack、XML、KDL、CSV 等格式避免各自漂移。JIT 尝试用 Cranelift 为 facet 做运行时代码生成尝试追上甚至超过 serde_json。arborium为 Rust 生态整理 tree-sitter 语法高亮与 grammar 分发。dodeca因为不满意现有静态站点生成器开始写自己的文档/站点系统。rapace为了避免动态链接和提升迭代速度设计基于 IPC/RPC 的多进程架构。tracey把规格、实现和测试建立可追踪关系。picante受到 salsa 启发做 async-first 的增量查询系统。字体、图片与文档体验font subsetting、WOFF2、fontcull、文档站点资源优化。pikru 与 aasvg-rs为文档系统补齐服务端图表渲染能力。facet 继续生长TypeScript、JSON Schema、错误派生、插件式代码生成。fs-kitty围绕 macOS FSKit、虚拟文件系统和跨语言 RPC 的实验。vixen最激动人心的新项目试图做一个兼具 Buck2 能力和 Cargo 体验的 Rust 构建系统。结尾这是一个不断 dogfooding、不断重新造轮子也不断把工具链变成自己想要样子的年份。2025 年对 fasterthanlime 来说是项目异常密集的一年。不是那种“做了一个项目然后写一篇复盘”的密集而是几个方向同时爆炸Rust 反射、序列化、静态站点生成、语法高亮、RPC、文档工具、字体裁剪、图表渲染、虚拟文件系统、增量计算、构建系统。每个项目一开始都像是为了修一个具体问题最后却牵出一整套工具链。这篇年度复盘的标题叫2025 Recap: so many projects直译就是“2025 回顾太多项目了”。这不是夸张。整篇文章像是一张项目依赖图facet需要文档站点于是有了dodecadodeca需要语法高亮于是有了arboriumdodeca依赖太重、迭代太慢于是需要进程隔离和 RPC于是有了rapacedodeca的构建和缓存越来越复杂于是又引出了picante和后来的vixen。每一个“只是想解决一下这个小问题”最后都会变成另一个项目。如果把这篇文章当作普通年终总结看可能会觉得它有些跳跃但如果把它当作一个工程师围绕“开发体验”不断构建自己理想工具链的记录就会发现主线非常清楚减少重复、减少等待、减少不透明让系统尽可能可解释、可缓存、可组合、可追踪。一、facet一切从不想再等 serde 编译开始2025 年最重要的项目是facet。它从 3 月开始到年底不过十个月但已经像经历了好几个生命周期。最初的动机很直接不想再等 Serde 生态里那些东西编译。Serde 是 Rust 生态事实上的序列化标准功能非常强但它的 derive macro、泛型代码、格式 crate 组合起来在大型工程中会带来不小的编译成本。问题不只是serde自己。一个类型派生Serialize或Deserialize时会生成不少泛型代码。真正调用serde_json::from_str::YourType()这类函数时泛型代码会被具体化rustc 和 LLVM 又要努力优化它。泛型实例化越多编译器要处理的东西就越多。对于依赖复杂、类型很多的项目来说这个成本很容易滚起来。在facet之前已经有过一次尝试叫merde。那个项目试图做一套类似 serde 的 trait但让 trait 支持 dyn dispatch从而减少泛型 monomorphization。后来回头看这更像是一个不如erased_serde的版本所以被放弃了。facet是第二次尝试。它基于一个不同的认识也许不应该把“序列化”作为核心抽象而应该把“反射”作为核心抽象。只要能在运行时知道一个类型长什么样是 struct、enum、list、map字段有哪些、偏移是多少、字段自己的 shape 是什么那么序列化、反序列化、调试打印、JSON Schema、TypeScript 类型生成等功能都可以建立在这套反射信息之上。于是facet的核心 trait 非常小一个类型实现Facet后会有一个 associated const叫SHAPE。这个SHAPE描述类型的结构。它不仅知道类型是什么 kind还知道字段、trait 实现、方法调用入口、list/map/set 的 vtable 等信息。这就是整个 facet 生态的基础。二、从SHAPE到Peek反射不是只读类型信息有了类型信息后下一步自然是读取已有值。facet里把这部分能力叫Peek。它允许你在运行时根据 shape 去观察一个具体值比如读出 struct 的字段、遍历 list、查看 enum 当前是哪一个 variant。这个方向直接支撑序列化JSON、YAML、TOML、Postcard 等格式都可以通过Peek来读取任意实现了Facet的类型。但反序列化更难。序列化是从已有值读数据反序列化是从外部格式构造一个值。构造值时会遇到很多部分初始化问题。比如一个 enum还没选定 variant 时内部状态是什么选了某个 variant 后variant payload 的字段是否已经初始化如果你后来换了另一个 variant之前已经初始化的字段怎么办如果解析中途失败哪些字段要 drop哪些字段还没初始化不能 drop这些问题都直接触及 Rust 的 unsafe 和内存安全边界。因此facet不是一个纯安全 Rust 小项目。它必须在底层写大量小心翼翼的 unsafe。能支撑这个过程的重要工具是 Miri。Miri 可以抓到很多 undefined behavior也能暴露一些看起来正常但实际上不安全的代码路径。项目里还用了 sanitizers、Valgrind 和 CI 性能测量。对于这种“部分初始化对象”的反射构造逻辑没有这些工具很难有信心继续推进。一旦底层 unsafe 代码写稳上层就可以构建很多东西。派生Facet时生成的大多是静态元数据和一些 vtable 适配函数。每个格式 crate比如facet-json、facet-yaml、facet-postcard只需要实现一套基于 shape 的逻辑就能服务所有实现了Facet的类型。相比 Serde 的泛型派生模式这是一条完全不同的路线。三、第一次失望运行慢、编译慢、体积还大理想很美但很快遇到了现实问题。最初对facet-json的测试显示它比serde_json慢不少大约在五到七倍范围内。这个结果虽然不好但还能接受因为反射本来就有运行时成本。只要编译更快、二进制更小整体权衡也许仍然成立。但后面真正测编译时间和二进制大小时结果更打击人它不仅运行时更慢编译也更慢二进制还更大。这就很尴尬。facet最初的一部分动机是减少泛型代码、减少编译负担。如果最终不仅性能差编译也差那就必须重新面对现实。文章里有一种很典型的工程诚实不能只讲愿景必须把数字拿出来。知道现状是改善现状的第一步。这段时间确实影响了对项目的热情。后来一段时间注意力转向了其他项目。但facet没有死反而在年底经历了一次重新定位。四、重新聚焦不一定要最小最快但要最好用2025 年 10 月左右有人开始把一个大型私有代码库从 serde 迁移到 facet。这个过程暴露了大量 bug也让项目进入了一段高强度修复期。几周里不断有新 issue 出现然后快速修复。这个真实用户迁移过程很重要因为它把项目从“概念验证”拉进了真实工程。其中一个核心问题仍然是 build time。迁移到 facet 后构建时间变差了。原因之一是 facet 生成的代码仍然不少另一个原因是很难完全逃离 serde、syn 这些生态依赖。即使你的业务代码不用 serde某些库可能仍然依赖 serde_json某个 derive macro 仍然依赖 syn某个 tracing 相关 crate 也可能把它们带进来。于是你没有真正摆脱旧成本只是在旧成本之上再加了 facet 的成本。这时项目策略发生变化不再执着于成为最小、最快编译、最快运行的 crate而是转向成为开发体验最好的 crate。这个转向带来了很多新功能。比如使用miette做更好的解析错误报告支持从AsyncRead做流式反序列化做facet-solver来处理 untagged enum、flattened struct 这种很难给出好错误信息的情况。因为 facet 有完整的 type shape可以看到所有候选 variant、字段结构和解析路径所以它有机会生成比普通格式 parser 更好的错误报告。这就是反射路线的优势当你不只是“驱动一个格式 parser”而是掌握完整类型结构时你可以做很多更智能的诊断。五、facet-format格式太多之后必须统一底座facet 生态里格式 crate 很多JSON、YAML、TOML、Postcard、MsgPack、XML、KDL还有 SVG、HTML、CSV、XDR、query string、命令行参数、ASN.1 等。格式一多就会出现一个常见问题功能漂移。比如 JSON 支持了 untagged enum 的某种错误提示但 YAML 没有TOML 支持了某种字段处理但 KDL 没有。每个格式 crate 都维护一套逻辑最终就会产生大量重复和不一致。为了解决这个问题项目引入了facet-format。它是facet-serialize和facet-deserialize的后继基础层所有格式 crate 都建立在它之上。这个重构很大意味着重写所有格式 crate把旧 crate 改名成 legacy再把新 crate 改回旧名字最后一口气删除十万行旧代码。这是非常激进的重构但目的很清楚让格式生态有统一基础不再每个 crate 各写各的。对一个快速扩张的生态来说抽出共同底层是必要的否则功能越多维护成本越高。六、又一次反转还是要把编译和代码体积做下来在转向“开发体验最好”的过程中又出现了一次反转既然最初说想更轻为什么要接受生成比 serde 更多代码、编译更慢这个现实于是又开始做代码生成量和编译成本优化。用的工具包括cargo-llvm-lines、rustc 的-Zmacro-stats、rustc self-profiling 等。目标不是凭感觉优化而是看清楚到底哪些宏、哪些函数、哪些泛型实例生成了多少 LLVM IR最终影响了多少编译时间和二进制体积。后来在一个 synthetic bloat benchmark 上facet 已经能做到比 serde 更快编译。虽然这不是所有真实项目的最终结论但至少证明方向不是死路。更重要的是项目建立了持续对比工具可以比较当前 facet 和过去 facet 在 LLVM IR 行数、编译时间、二进制大小等指标上的变化。还做了一个基于ratatui的 TUI 来查看这些记录。这说明项目进入了一个更成熟的阶段不再只是“感觉慢”或“感觉快”而是有指标、有历史、有回归检测。七、JIT用 Cranelift 给反射系统补性能facet 运行时慢的问题仍然存在。最初曾经提过一个想法既然反射慢那是否可以 JIT也就是在运行时生成针对具体类型的代码这个想法拖了一段时间后来终于真正开始做。用的是 Cranelift。Cranelift 是一个低层代码生成库可以把中间表示翻译成机器码。facet 的 JIT 分成两个层次。第一层可以叫 tier-one JIT。它对所有格式都有帮助不再通过反射逐字段赋值而是生成直接赋值的代码。这样能减少反射调度成本。第二层可以叫 tier-two JIT。它更深入由具体格式 crate 参与。例如facet-json知道如何解析 JSON就可以直接生成“解析 JSON 并构造目标类型”的机器码把格式解析和字段赋值融合在同一段代码里。这就能得到更明显的性能提升。当然这有很多 caveat。JIT 生成的代码难以调试可能包含 undefined behavior可能崩溃。Cranelift 生成代码也没有 LLVM 那种自动向量化和标准库内联能力所以需要很多手工技巧比如 staging buffer、Vec::from_raw_parts、一次性构造 HashMap 等。但结果很有趣在某些 JSON 场景下facet 生态内的 JIT 方案已经能超过serde_json。还做了性能 dashboard用divan测时间用gungraun测指令数。不过某些结果看起来太好比如 postcard 的某些数字可能有零拷贝 apples-to-oranges 的问题所以仍然需要谨慎验证。这部分体现了 fasterthanlime 一贯的风格先承认风险再把疯狂想法做出来然后用数据看它到底行不行。八、arborium为 Rust 文档生态整理 tree-sitter 语法高亮与此同时另一个项目是arborium。起因很简单Rust 文档里有 KDL、TOML 等代码块但 docs.rs 上经常没有合适的语法高亮。另一方面一些私有项目也需要基于 tree-sitter 做语法高亮。但 tree-sitter grammar 分散、编译到 Wasm 麻烦、C 代码和 Rust toolchain 之间经常有各种坑。于是决定做一个 Rust 生态里比较完整的 tree-sitter 和 grammar 分发方案。工作量很大整理 96 个 grammar设计 API确保它们都有 syntax highlighting query打包主题做 landing page保留 license 和 attribution还要确保这些 grammar 能编译到 WebAssembly。为了让 C 代码能在 Wasm 环境里编译需要伪造一些它们声称需要的 C 函数。CI 自动化也很重要因为 grammar 和主题都需要持续发布。最后甚至要联系 crates.io 团队说明自己可能会一次发布 100 个 crate。arborium的意义在于把一个零散、重复、容易出错的问题集中解决。以后需要 Rust 里用 tree-sitter 做高亮不必每次都重新追 grammar、修编译、处理主题和 license。它也变成了后面dodeca的基础设施之一。九、dodeca不满意静态站点生成器于是写一个新的为了给 facet 做一个像样的网站和文档需要静态站点生成器。Rust 生态里有 Zola大家也很喜欢 Zola。但 fasterthanlime 对网站开发体验、输出结果、插件机制和缓存都有很强的个人偏好。问题在于Rust 生态里的静态站点生成器很难做插件。在 JavaScript 里可以 eval在 Ruby 里可以 require在 Python 里可以折腾路径但 Rust 里插件机制就麻烦很多。如果想加插件要么搞动态链接要么搞 RPC要么做一套复杂架构。另外静态站点生成器的缓存也常常不理想。它们可能缓存了已经 stale 的东西又可能没有缓存本该缓存的东西。HTTP header 也经常过于保守。对于非常在乎构建速度和资源产物的人来说这会让人很难受。于是有了dodeca名字来自 dodecahedron也就是十二面体。一开始看起来只是把 Markdown 转成 HTML。但很快需求扩张语法高亮可以用刚做好的arboriumHTML、JavaScript、CSS 要内置 minification资源要做 cache-busting图片处理要内置输入 PNG按浏览器支持输出 JPEG-XL、AVIF、WebP还要处理链接、script、img 标签让它们指向带 hash 的缓存友好 URL。做到某个阶段依赖已经膨胀到 1200 个左右迭代变得很痛苦。过去曾经做过rubicon来解决动态链接问题但这次不想再碰动态链接。那下一个选择就是 IPC。于是dodeca又引出了下一个项目rapace。十、rapace把单体工具拆成一堆 cell用 RPC 连接rapace是一个 RPC/IPC 项目。名字是 RPC 加了几个字母同时也是法语里的“猛禽”。它的背景是 dodeca 的复杂度。一个静态站点系统里有 HTML 修改、图片压缩、HTTP serving、TUI、模板展开、开发工具等很多部分。如果所有东西都链接到一个二进制里依赖会非常重重新编译会非常痛苦。与其动态链接不如把核心 app 做成 hub把周围每个功能做成单独 binary也就是一个个 cell。这样HTML 修改是一个 cell图片压缩是一个 cellHTTP server 是一个 cellTUI 也是一个 cell。dodeca当时已经有 18 个 cell。但进程拆开后通信就变成关键问题。普通 RPC 如果大量拷贝数据性能会很差。比如压缩大图片如果像素数据在 RPC 系统里复制好几遍非常浪费。因此rapace设计了共享内存方案共享内存区域像 allocator buffer pool一块 buffer 当前属于谁要被追踪。发送大 payload 时可以用 handle 或 reference 传递而不是复制内容。它还支持 zero-copy deserialization收到 frame 后反序列化结果直接借用 frame 内部数据然后把 frame 和反序列化 payload 一起携带避免拷贝。这有点像yokecrate 的思路但没有用 yoke因为 yoke 依赖 syn。rapace使用facet-postcard做序列化和反序列化这让服务发现、动态调用 endpoint 更容易。后来因为有了 RPC 语义又自然扩展出 WebSocket transport、generic stream transport、in-memory transport 等。它不仅服务于本机 dodeca cell也可以用于 devtools 和 dev server 通信甚至未来用于 Kubernetes 集群服务之间通信。这时项目又需要一份正式规格而不是继续凭感觉写。于是引出了tracey。十一、tracey规格、实现、测试之间要能互相追踪tracey的核心概念是 traceability也就是可追踪性。一个系统有 specification有 implementation有 tests。理想情况下每条需求在规格里都有唯一标识代码里能标注“这一段实现了哪个需求”测试里也能标注“这个测试覆盖了哪个需求”。最后可以交叉检查规格中的每条要求是否都被实现是否都有测试代码里的实现是否能追溯到规格这对协议类项目很重要。rapace如果想有其他语言实现就不能只靠 Rust 代码作为事实标准。它需要明确规格而且规格和实现之间要能保持同步。tracey就是围绕这个目标构建的工具。它可以在规格文档、源代码和测试之间建立链接生成视图让你看到每条规则在哪里被实现、在哪里被测试。这种方法很适合 RPC 协议、构建系统、格式规范等长期演进项目。它背后的思想也延续了整篇文章的主线工具不只是“能跑”还要能解释自己、能验证自己、能让人知道覆盖情况。十二、picanteasync-first 的增量计算系统dodeca 还需要一个核心能力增量计算。静态站点生成器不应该每次都从头构建所有页面、所有图片、所有字体。它应该知道哪些输入变了哪些输出需要重新计算哪些东西可以从缓存拿。Rust 生态里有salsa它是很有名的增量计算框架。picante不是 fork而是借鉴同类思想做一个 async-first 的版本。它为 dodeca 这种系统服务大量任务可以异步执行同时又需要依赖追踪、缓存、增量更新。picante使用 facet 做很多基础工作比如结构相等比较。即使你的类型没有实现PartialEq它也可以基于 facet 反射做 structural equality。这说明 facet 不只是序列化工具它逐渐变成很多项目的基础反射层。picante还支持把 cache 持久化到磁盘而且是增量持久化不需要等结束时一次性保存。对大型文档站点或资源处理系统来说这很重要。你不希望 dev server 每次重启都丢掉所有缓存。在 dodeca 中生产构建和开发构建的差异很小。默认会做 JS/HTML/CSS minification会按浏览器支持做图片压缩会处理字体子集。很多任务都可以表达为 query把 Markdown 渲染成 HTML从 HTML 中提取 code point把所有页面用到的 code point 聚合成集合再用未压缩字体和 code point 集合生成 font subset。只要没有用到新字符就不需要重新裁剪字体。这种“所有事情都是 query所有依赖都可追踪”的思路最终也和后面的 vixen 构建系统方向相呼应。十三、字体子集从 2MB 到 10KBdodeca 的一个有意思功能是 codepoint-accurate font subsetting。也就是根据整个网站真正用到的 Unicode code point生成最小字体子集。比如 facet.rs 使用 Iosevka 字体原始字体带 NerdFonts 等内容可能有 2MB但最终服务给页面的字体可以压到 10KB 左右。这件事听起来简单但实际需要解析样式、理解哪些字体用于哪些内容、收集所有页面中的字符集合再对对应字体做裁剪。过去常用工具可能是glyphhanger但它多年没更新依赖很旧的 Playwright旧到连浏览器都下载不了了。于是又开始用 Rust 重写。期间还发布了woofwoof它是 WOFF2 C 实现的包装确保 Linux、Mac、Windows CI 都能构建。又发布了fontcull作为 Rust 版字体子集工具并引入 Google fontations 相关 crates。这部分很好地体现了这篇文章的风格遇到一个工具坏了不是绕过去而是把底层也打包好、修好、做成 Rust crate放进自己的系统里。十四、pikru把 pikchr 移植到 Rustdodeca 还需要画图。技术文档经常需要 diagram。常见方案是 Mermaid但 fasterthanlime 不喜欢客户端渲染因为它影响页面加载、可访问性也可能造成布局跳动。看过一些替代方案后发现了pikchr。它有一个自包含的 C 实现很适合移植到 Rust。不过手工移植很枯燥于是把 Claude 当成移植助手给它工具让它能比较 C 实现和 Rust 实现的渲染结果。为了做到这一点需要生成比较 HTML对每个 test case 展示两边 SVG 的视觉对比包括 side-by-side、onion skin 等。这既给人看也给 AI agent 看。还做了一个 MCP让模型可以运行单个测试把两个 SVG 渲染成 PNG因为模型有视觉能力可以直接看图发现线条位置不对。如果只是比较 SVG markup模型很容易被大量标记淹没。为了让 agent 能更好地定位问题还写了一些 tree diff 算法生成足够好的 diff让它知道“到底哪里错了”。最终 Rust port 达到 100% output parity也就是输出完全对齐而不只是测试覆盖。这个 Rust 实现叫pikru。这部分很有意思因为它不是简单“让 AI 写代码”。真正关键的是给 AI 建测试环境、比较工具、可视化反馈和可执行闭环。没有这些工具模型很难把一个图形渲染器移植到完全一致。十五、aasvg-rs再移植一个 ASCII 图渲染器完成pikru后又发现 Claude 和 GPT 都不擅长写 PIK diagram。如果每张图都要自己写而 PIK 自动布局又不多那仍然不够理想。于是又找了另一个方案aasvg。它来自 markdeep可以把 ASCII art diagram 渲染成漂亮 SVG。后来发现其实也有svgbob这类工具但当时的研究路径先找到了 aasvg于是就移植了。Rust port 叫aasvg-rs。它也达到了和原始实现的 parity并且像pikru一样使用 CSS variables 支持 light/dark SVG。这里还吐槽了一下 Safari Mobile 不支持某些能力像当代 Internet Explorer。这一段和pikru一起构成了 dodeca 文档系统的图表能力不是客户端渲染而是在构建时生成 SVG不是依赖巨大工具链而是把 C/JS 实现移植到 Rust并做成可控的构建组成部分。十六、facet 继续扩张TypeScript、JSON Schema、错误派生与插件文章后半段又回到 facet。做 facet benchmark dashboard 时前端 JavaScript 数据结构经常和后端 benchmark 输出格式不同步。理想状态是后端 Rust 类型是 source of truth然后自动生成 TypeScript 类型让前端有类型检查同时生成 JSON Schema验证后端输出确实符合预期。生态里已经有schemars这样的 derive macro但 facet 的目标是成为“最后一个你需要派生的宏”。既然已经有类型 shape就应该能生成 TypeScript 和 JSON Schema。于是很快写出了facet-typescript和facet-json-schema让 benchmark dashboard 迭代更舒服。接下来又想做基于 facet 的thiserror替代、displaydoc替代、miette Diagnostic 派生等。但这里遇到一个边界有些 trait 不能只靠运行时反射实现因为它们需要真的生成代码。例如Error、Display、Diagnostic都需要 trait impl。于是 facet 需要一种插件系统复用 facet macro 已经解析过的类型定义结果再用模板生成额外 impl。这样就不需要另一个 derive macro也不需要再让另一个宏重新依赖 syn 解析一遍类型。这套系统还不完全最终版模板也比较简单但已经能工作。思路很明确很多性能不关键的 trait比如 Debug如果可以基于 reflection 实现就能减少代码生成、编译时间和最终二进制体积。尤其 Debug 对大型 struct 可能生成大量代码如果用facet-pretty查看内部结构就不必每个类型都派生 Debug。这又回到 facet 的核心目标一个派生尽可能服务更多开发体验场景。十七、fs-kitty围绕 macOS FSKit 的虚拟文件系统实验fs-kitty是一个和虚拟文件系统相关的项目也和rapace有关。虚拟文件系统有很多场景。Linux 上常见 FUSE也就是 file system in user space。macOS 上过去可以写 kernel extension但内核扩展风险高Apple 也长期在限制它。后来 Apple 推出了 FSKit让文件系统可以在用户态实现并通过 XPC 和内核通信。问题是它要求你打包成 filesystem extension也就是.appexbundle还要通过一个普通.app注册到系统设置里。这对命令行工具不友好。有人做了 FSKitBridge在文件系统扩展和你的 binary 之间再加一层 RPC让你可以用任意语言实现文件系统。但 fasterthanlime 对安装别人写的 filesystem extension 不放心即使它在用户态也感觉不对。于是自己做了fs-kitty使用rapace做 RPC。最初app 和 extension 大部分是 Swift少部分 Rust 通过swift-bridge链接进去。但后来遇到 hang 和 crash于是开始考虑把更多逻辑改成 Swift并且实现 rapace 的其他语言版本。比如 TypeScript 实现也变得很有吸引力因为有些场景下前端用 Svelte 和 TypeScript 更舒服不想所有东西都用 Rust。这部分展示了 rapace 的另一个价值如果协议足够清楚就可以跨语言实现。Rust 可以是后端或核心也可以只是生态里的一部分。十八、vixen最激动人心的项目重新思考 Rust 构建系统文章最后一个大项目是vixen。这是 2025 年最让他兴奋的方向也会延续到 2026。起因是把整个 monorepo 从 Cargo 迁到 Buck2 的经历。这个过程很开眼Cargo 的缓存能力并不理想至少不是一个“proper build system” 应该有的形态。这里的 proper 带有很强个人定义但核心观点是构建系统应该有严谨依赖追踪、内容寻址存储、远程执行、可解释缓存、构建图并行而不只是把 target 目录堆在那里。他在自己的 monorepo 上比较了一些数字冷构建 Cargo 35 秒Buck2 25 秒no-op 构建 Cargo 接近 1 秒Buck2 0.06 秒改依赖树深处一个函数Cargo 21 秒Buck2 8.5 秒。数字说明 Buck2 在缓存和增量构建上确实更强。但 Buck2 的问题是维护 BUCK 文件太痛苦。一个有上百个 crate 的 monorepo要维护每个 crate 的 BUCK 文件还要维护依赖 fixup很折磨。于是自然产生一个想法能不能有一个构建工具具备 Buck2 的性质又有 Cargo 的开发体验不用单独生成依赖构建文件也不用重复写构建系统本该自己发现的信息而且从一开始就为 Rust 生态友好设计。这就是vixen。它不是要替代 Cargo。Cargo 会一直存在因为它要服务所有 Rust 用户、所有平台、强 backwards compatibility。vixen更像是一个从零开始的实验把想要的构建系统性质组合起来。它从第一天就考虑 remote execution 和 content-addressable store。这也是为什么它现在连最简单 hello world 都很难跑通因为移动部件太多。但一旦这套模型成立就不需要担心 target 目录也不需要担心 feature 开关导致同一个 target 目录被不同构建覆盖。所有输入都有 hash工具链也作为输入构建产物进入内容寻址存储。如果 CI 和本地使用同一套平台和远程执行器理论上本地已经构建过的东西到 CI 时可以是 no-op。CI 不需要用 YAML 拆多个 job不需要临时上传 artifact 再下载因为所有东西都在内容寻址存储里。构建图可以由 orchestrator 自动最大化并行而不是靠手工 fan-out/fan-in。为了真正 hermeticC/C 工具链可以从 Zig 拿rustc 工具链直接从 static.rust-lang.org 下载不经过 rustup。因为在 Cargo/rustup 世界里即使 stable 和 1.92 指向同一个 toolchain只要路径不同也可能导致重建而在 hermetic build system 中工具链 hash 一样就是一样路径由虚拟文件系统挂载输入不变就不需要重建。它还希望对 build script 更聪明。Buck2 往往要求显式声明很多输入输出但 Rust crate 生态中有很多模式是可以识别的。比如 build script 只调用cccrate那构建系统可以 patchcccrate让它不真的编译而是生成后续由 orchestrator 执行的 action。对特殊 crate比如需要网络访问的 sqlx、需要 assembly 的 rustls可以做 special treatment。甚至可以把 package manager 和 fixup 机制内置到构建系统里。这一段是整篇文章最有野心的部分。它承认项目非常难最可能的结果是烧尽热情、做不出有用东西。但也明确表达已经围绕 CI build time 做了太多 hack不想再只打补丁想从底层重新设计。十九、这篇复盘真正想表达什么表面上看这篇文章像一个项目清单facet、arborium、dodeca、rapace、tracey、picante、pikru、aasvg-rs、fs-kitty、vixen。每个项目都有自己的名字、主页、crate、实现细节。但真正的主线不是“我做了很多项目”而是“我不断把自己不满意的开发体验替换成自己能控制的系统”。不满意 serde 编译成本就做 facet。不满意格式 crate 功能漂移就做 facet-format。不满意运行时反射性能就做 Cranelift JIT。不满意 docs.rs 的代码块高亮就做 arborium。不满意静态站点生成器的插件和缓存就做 dodeca。不满意动态链接和巨大依赖导致迭代慢就做 rapace。不满意规格和实现之间断裂就做 tracey。不满意站点构建无法精准增量就做 picante。不满意字体工具老旧就做 fontcull。不满意客户端 diagram 渲染就做 pikru 和 aasvg-rs。不满意 macOS FSKit 的使用体验就做 fs-kitty。不满意 Cargo 缓存和 CI 体验就做 vixen。这些项目并不是互相孤立的。它们像一组互相喂养的工具facet 为 rapace、picante、dodeca、schema/type generation 提供反射arborium 给 dodeca 提供高亮dodeca 暴露构建性能问题推动 rapace 和 picanterapace 推动 tracey 和 fs-kitty构建和 CI 痛点最终汇聚到 vixen。这也是 dogfooding 的力量。每个项目都被另一个项目使用所以问题会很快暴露也必须自己修。文章结尾有一个很重要的态度如果是我的错那我反而高兴因为我知道我能修如果是别人的错那就不知道什么时候能修。二十、2025 年的技术关键词这篇复盘里可以提炼出几个关键词。第一个是reflection。facet 的核心就是给 Rust 类型提供可用的运行时结构信息。这个能力从序列化扩展到错误报告、schema、TypeScript、Debug、JIT、格式生态统一。第二个是DX开发体验。很多项目不是为了理论完美而是为了让开发更舒服错误信息更好、文档更好、构建更快、站点生成更可控、插件更容易、CI 更少等待。第三个是caching。从 facet 编译成本到 dodeca 静态资源到 picante 增量 query再到 vixen 的 content-addressable store缓存和增量构建是贯穿全年最强的主题之一。第四个是tooling ownership。遇到不满意的工具不只是绕开而是重新做一个。这个倾向当然有风险很容易过度造轮子但也能带来高度一致的系统。第五个是specification and traceability。rapace 发展到跨语言实现后必须有规格tracey 进一步要求规格、实现和测试互相链接。这说明项目不只是玩具而是开始面对长期维护问题。第六个是AI as engineering assistant。在 pikru 移植中AI 不是简单替人写代码而是在测试工具、视觉对比、MCP、diff、反馈循环的帮助下参与移植。这是比较现实的一种 AI 编程方式给模型一个可验证环境而不是盲信它的输出。第七个是build system ambition。vixen 代表了对 Rust 构建体验的最大野心不是修 Cargo 的某个问题而是探索一个内容寻址、远程执行、hermetic、缓存可解释、Rust 友好的新构建系统。二十一、结尾大量项目背后的快乐这篇文章最后并没有给出一个“2025 年完成了什么2026 年目标是什么”的正式 OKR 式总结。它更像是承认这一年很快乐明年会更快乐。每一个 crate 都在使用另一个自己的 crate这种 dogfooding 很爽因为可以得到自己想要的开发体验如果坏了也只能自己修。所有提到的项目基本都在 bearcove GitHub organization 下开源。只是不要期待所有东西都稳定。facet 是相对更值得关注的项目其他项目如果真正变得可用会有正式博客和视频宣布。如果还没有正式宣布就说明它还在实验中。这句话其实很重要。整篇文章里的项目很多看起来都很野但并不等于每个都应该马上拿去生产用。它们更像是一组正在生长的工具一边服务作者自己的项目一边探索 Rust 工具链可能的形状。这篇 2025 复盘最大的价值不只是知道 fasterthanlime 做了哪些项目而是看到一种工程师文化对等待不耐烦对重复不耐烦对不透明不耐烦对“差不多能用”不满意。于是不断拆解、重写、测量、dogfood、再重写。它有很强的个人偏执也有很强的工程创造力。如果用一句话概括这一年那就是2025 年fasterthanlime 没有只是在写 Rust crate而是在试图把整个开发环境从类型反射到文档站点从 RPC 到构建系统都改造成自己想要的样子。