一个编解码工具通常会被实现很多遍:网页版一套 JS,桌面版一套,AI 调用时再让模型自己想办法。每多一个消费者,就多一份行为可能不一致的实现。
我们的做法是反过来:算法只写一次,放在 Rust 内核里,然后编译成不同形态去适配不同消费者。
内核是纯函数,没有 IO
newtool-core 里的算法全部是纯函数——输入数据、输出数据,不读文件、不发网络请求。这个约束有两个好处:一是可以编译到 wasm 在浏览器里跑,二是攻击面极小。
crates/newtool-core
01// 纯函数:无 IO,可编译到原生与 wasm02pub fn decode_jwt(token: &str) -> Result<JwtParts, Error> {03 let (h, p, s) = split(token)?;04 Ok(JwtParts {05 header: b64url_decode(h)?,06 payload: b64url_decode(p)?,07 signature: s.to_string(),08 })09}清单是唯一事实来源
哪些算法存在、参数是什么、在哪些形态下生效——这些信息写在 manifests/algorithms.toml 里,而不是散落在代码注释或文档里。清单驱动代码生成,也驱动可见性。
manifests/algorithms.toml
01[[algorithm]]02id = "jwt.decode"03name = "JWT 解码"04status = "rust" # 强制与 registry 同步05forms = ["app", "cli", "web-wasm", "web-server"]0607[[algorithm.params]]08name = "token"09type = "string"10required = truebuild.rs 在构建期比对清单与注册表。如果清单里声明了某个算法但代码里没有实现(或反之),构建直接失败。这类校验放在构建期而不是 code review 里,是因为它会一直生效。
三个消费者,一份实现
- AI:通过 skills 技能包或 MCP 端点调用,list / describe / run 三步
- 人类:Tauri 2 桌面 App,通过 kernel-client 的 tauri adapter 调用
- 浏览器:内核编译为 wasm,断网也能用,数据不出浏览器
桌面壳的 kernel-client 有三个 adapter(tauri / http / wasm),指向的是同一个内核接口。换 adapter 就是换运行环境,算法行为不变。
代价是什么
- Rust 的迭代速度比脚本语言慢,写新算法门槛更高
- 清单与代码的同步需要额外的构建流程与纪律
- wasm 目标下不能用某些平台特性,算法实现要主动规避
多端行为漂移是那种「出了问题很难查、不查又一直存在」的缺陷。用构建期约束把它消灭在发生之前,比事后对齐便宜得多。