Ruby 模式匹配(Pattern Matching)完全指南
Ruby 3.x 的模式匹配让复杂数据解构一行搞定。本文讲解数组/哈希/对象模式、嵌套匹配、as 绑定、pin 变量钉扎与守卫条件,附大量实战示例。
模式匹配(case/in)是 Ruby 2.7 引入、3.0 正式稳定的特性:不只匹配值,还能匹配数据结构形状并顺手把字段绑定到变量。处理嵌套 JSON、消息分发、多态参数时,它能把嵌套 if 压成扁平的声明式分支。
从 case/when 到 case/in
传统 case/when 按值分支;case/in 按"形状"分支:
case response
in { status: 200, body: body }
puts "成功:#{body}"
in { status: 404 }
puts '未找到'
in { status: Integer => s } if s >= 500
puts "服务器错误 #{s}"
else
puts '未知响应'
end
每个 in 子句同时做三件事:检查结构、匹配值、绑定变量(如 body、s)。不匹配时抛出 NoMatchingPatternError,所以 else 兜底很重要。
数组模式
case point
in [0, 0] # 原点
'原点'
in [Integer => x, 0] # x 轴上
"x 轴 #{x}"
in [Integer, Integer] => coords
"一般点 #{coords}"
in [*, Integer => last] # 任意长度,只看末位
"末位 #{last}"
end
* 匹配任意剩余元素,*rest 把剩余绑定成数组。精确长度匹配不加 * 即可。
哈希模式:处理 JSON 的杀手锏
case event
in { type: 'click', payload: { x:, y: } }
"点击 (#{x}, #{y})"
in { type: 'scroll', payload: { depth: Integer => d } } if d > 80
'深度滚动'
end
{ x:, y: } 是 { x: x, y: y } 的简写——键名即变量名。哈希模式默认子集匹配(多出的键忽略),要求精确匹配用 **nil 禁止额外键,**rest 捕获剩余键。
对象模式与守卫
对象也能匹配,用 [] 解构:
case time
in Time[wday: 1..5, hour: 9..18]
'工作时间'
end
对象模式依赖对象的 deconstruct_keys 方法(Struct、Data、Time 等内置支持,自定义类可实现该方法接入)。守卫条件用 if/unless 追加在模式后,表达"结构对且值满足条件"。
as 绑定与 pin 运算符
in { user: { name: } } => whole # as 模式:解构同时保留整体
in ^expected_status # pin:匹配外部变量的值而非绑定新变量
=> whole 把匹配到的完整结构再绑定给一个变量;^var 钉扎已有变量,表示"必须等于这个值"——没有 ^ 的同名单词会被当作新绑定,这是最常见的踩坑点。
右向赋值:=> 的单行用法
{ id: 1, name: 'demo' } => { id:, name: }
puts "#{id}-#{name}"
expr => pattern 单行解构赋值,等价于只含一个分支的 case/in。解析配置、接口返回时写起来非常顺手;不需要报错场景可用 in 右向形式(不匹配返回 false 而非抛异常)。
常见问题(FAQ)
Q:Ruby 2.7 能用模式匹配吗?
2.7 是实验特性(会打印警告),3.0 起正式稳定。新项目直接放心用,老项目升级前先确认 Ruby 版本。
Q:模式匹配和正则表达式什么关系?
完全不同:正则匹配字符串文本,模式匹配匹配数据结构。文本解析用正则,数据形状判断用模式匹配,两者可以组合(模式内嵌正则:in { email: /\A.+@.+\z/ })。
Q:性能如何?能用在热路径吗?
编译后的模式匹配开销与等价 if 链相当,热路径可用。真正的注意点是可读性——模式嵌套超过三层时,拆成多个方法或子模式反而更清晰。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。