Sorbet 和 RBS 怎么选?Ruby 两大类型体系深度对比
Sorbet 注解内嵌源码带运行时检查,RBS 签名独立成文件纯静态分析。本文从语法形态、类型推导、生态成熟度、错误信息、编辑器体验与生产风险全面对比,给出明确选型建议。
大型 Rails 项目、要成熟工具链与运行时保障选 Sorbet(Stripe/Shopify 验证,生态碾压级领先);追求官方标准、喜欢类型与实现分离、库(gem)开发场景选 RBS。2026 年的现实是:应用层 Sorbet 占主流,RBS 作为公共接口描述语言与官方方向长期共存。
根本分歧:类型写在哪里
Sorbet 把注解写在 Ruby 源码里:
sig { params(amount: Integer).returns(String) }
def format_price(amount)
"¥#{amount / 100.0}"
end
RBS 把签名写在平行的 .rbs 文件:
class PriceFormatter
def format_price: (Integer amount) -> String
end
内嵌派的理由:类型就在代码旁边,review 时看得见、改方法时同步改;分离派的理由:实现保持纯净,类型可以描述"源码之外的形状"(如 C 扩展、高度动态的方法)。这不是审美之争——它决定了重构体验、review 流程和工具链的一切。
运行时检查:Sorbet 的独有安全网
Sorbet 的 sig 不只是静态注解:sorbet-runtime 在运行时也校验参数与返回值(测试/开发环境全开,生产可采样)。类型签名写错了、检查器没覆盖的动态调用路径,运行时还有一道网。RBS 是纯静态——检查工具(Steep)之外的运行时行为与签名无关,写错的签名不会自己暴露。
类型推导与日常体验
Sorbet 的推导更"聪明":局部变量、流敏感收窄、常见的 Ruby 惯用法开箱支持,无注解代码也能给出有用结论;RBS 推导依赖签名完备度,untyped 多了之后检查趋近于无。错误信息方面 Sorbet 经过大企业多年打磨(精确的冲突位置 + 修复建议),Steep 的报错对新手相对晦涩。
生态成熟度:决定性的现实差距
- gem 签名:Sorbet 有 tapioca 自动生成 RBI,社区共享 RBI 仓库覆盖主流 gem;RBS 有 gem_rbs_collection 官方仓库,覆盖面在追赶中;
- Rails 支持:Sorbet 的 sorbet-rails/tapioca 处理 ActiveRecord 动态方法成熟;RBS 的 rbs_rails 可用但边角情况更多;
- 编辑器:Sorbet 自带 LSP(跳转、补全、实时错误)体验接近 TypeScript;RBS 侧 Steep 的 LSP 在进步但顺滑度有差距。
渐进迁移与团队学习
两者都支持渐进:Sorbet 用 typed: 文件级别阶梯,RBS 用 untyped 过渡。Sorbet 的学习曲线更平缓(语法贴近 Ruby),RBS 要单独学一门签名语言——但 RBS 的类型表达能力(接口、高阶泛型)上限更高,库作者描述公共 API 时更精确。
生产风险画像
Sorbet 的风险在运行时校验的性能开销(可控可关)与 RBI 滞后(gem 升级后签名未同步的误报);RBS 的风险在签名与实现悄悄漂移(纯静态无运行时兜底,靠 CI 纪律维持)。两者都比"没有类型"强一个量级——选型的边际差异远小于"要不要类型"的决策本身。
常见问题(FAQ)
Q:两个体系能互转或共存吗?
不推荐共存(双重维护负担)。互转有工具辅助(sorbet-to-rbs 类实验性工具),实质迁移仍以人工重写签名为主,选定前在核心模块上各试点一周。
Q:写 gem 该选哪个?
优先 RBS:它是 Ruby 官方签名格式,gem 附带 sig/ 目录即可被所有 RBS 工具消费;同时可以用 sorbet 的 rbi 导出服务 Sorbet 用户——库的公共 API 用 RBS 描述正成为社区惯例。
Q:TypeProf 在这盘棋里是什么角色?
TypeProf 是 RBS 阵营的"签名生成器":分析 Ruby 源码自动产出 RBS 草稿,降低手写成本。Sorbet 阵营对应的是其内置推导 + tapioca。两者都解决"老代码冷启动"问题。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。