RBS 入门:Ruby 官方类型签名语言详解
RBS 是 Ruby 3.0 起自带的官方类型签名语言,类型写在独立的 .rbs 文件里不污染源码。本文讲解 RBS 语法、Steep 静态检查、TypeProf 自动生成签名的完整工作流。
RBS 是 Ruby 官方的类型描述语言:在与源码平行的 .rbs 文件中声明类、方法的类型,配合 Steep 做静态类型检查、TypeProf 自动推导签名。它让 Ruby 在不改动动态本性的前提下获得编译期类型保障,适合大型长期项目渐进引入。
RBS 的定位:官方但克制
Ruby 社区的类型方案不止一种(Sorbet 也流行),RBS 的独特地位在于它是 Ruby 核心团队维护的官方方向。设计取舍很"Ruby":类型与实现分离——.rb 文件保持原样,类型信息写在 sig/ 目录的 .rbs 文件里。喜欢代码即类型的人嫌它割裂,喜欢关注点分离的人觉得恰到好处。
第一个 RBS 签名
项目结构:
lib/user.rb
sig/user.rbs
# lib/user.rb
class User
def initialize(name, age)
@name = name
@age = age
end
def adult?
@age >= 18
end
end
# sig/user.rbs
class User
def initialize: (String name, Integer age) -> void
def adult?: () -> bool
end
方法签名语法:方法名: (参数类型 参数名) -> 返回类型。实例变量、常量、类方法(def self.)、继承与模块混入都有对应语法。
常用类型速查
def process: (String input) -> Array[String] # 泛型数组
def find: (Integer id) -> User? # 可空类型
def each: () { (String) -> void } -> void # 块类型
def call: (String | Integer id) -> void # 联合类型
def config: () -> { timeout: Integer, retries: Integer } # 结构哈希
接口(interface)描述鸭子类型、type alias 给复杂类型起别名——表达力覆盖主流静态语言的常用场景。
用 Steep 做静态检查
RBS 本身只是"说明书",检查靠工具。Steep 是官方配套的类型检查器:
gem install steep
Steepfile 声明源码与签名目录后运行 steep check,类型不匹配(比如把 String 传给声明为 Integer 的参数)会在 CI 阶段被拦下。渐进采用的正确姿势:先给核心领域模型写签名,steep check 只覆盖已签名的部分,随时间逐步扩大范围。
TypeProf:自动生成签名草稿
手写全部签名太痛苦?TypeProf(Ruby 自带)能分析源码自动推导 .rbs:
typeprof lib/user.rb
生成结果是"尽量宽"的草稿(很多 untyped),人工收紧后交给 Steep 持续检查。工作流:TypeProf 生成草稿 → 人工精修 → Steep 入 CI。
RBS 与 Sorbet 的取舍
Sorbet(Shopify/Stripe 系)把类型注解写在 Ruby 源码里(sig { }),带运行时检查,工具链(LSP、自动修正)更成熟;RBS 分离文件、纯静态、官方背书。两者都在演进且社区并存——大型新项目两选一都可,关键是一旦选定就别混用。
常见问题(FAQ)
Q:Rails 项目能用 RBS 吗?
可以但成本高:Rails 的动态魔法(ActiveRecord 动态方法)需要大量签名补丁,社区有 rbs_rails 等生成器辅助。实务上 Rails 大项目用 Sorbet 的更多(Shopify 大规模验证过)。
Q:untyped 是不是白写了?
不是。渐进类型的意义就在"有签名的部分有保障":untyped 是显式的"暂未标注",随着维护逐步收紧。Steep 可以配置对 untyped 的严格级别。
Q:第三方 gem 没有签名怎么办?
官方维护的 rbs gem 仓库(gem_rbs_collection)收录了主流 gem 的签名,Steepfile 里声明即可引入;冷门 gem 可以自己写或暂时 untyped 隔离。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。