Ruby 的 Block、Proc 和 Lambda 有什么区别?一次讲透
Block、Proc、Lambda 是 Ruby 最具表现力的特性,也是新人最容易混淆的概念。本文用实例讲清三者的定义、差异(参数检查、return 行为)与闭包原理,帮你写出地道的 Ruby 代码。
Block 是跟在方法调用后的一段匿名代码块,本身不是对象;Proc 是把 Block 封装成的对象,可以赋值、传参、重复调用;Lambda 是更严格的 Proc——会检查参数个数,且 return 只退出自身。日常开发中迭代用 Block,回调存起来用 Proc,需要严格语义时用 Lambda。
Block:方法背后的代码块
Block 是依附于方法调用的代码片段,用 do...end 或 { } 包裹:
[1, 2, 3].each do |n|
puts n * 2
end
Block 不是对象,不能赋值给变量,只能"附身"在方法调用上。它是 Ruby 迭代、资源管理(File.open)、DSL 构建的基石。
让方法接收 Block:yield 与 &block
自定义方法接收 Block 有两种写法。隐式用 yield:
def measure
start = Time.now
yield
puts "耗时 #{Time.now - start} 秒"
end
measure { sleep 1 }
显式用 &block 把 Block 转成 Proc 对象,好处是可以把它继续传给别的方法:
def run(&block)
block.call
end
Block 参数还可以带默认值、捕获多个值(|a, b|),Hash 迭代时 |key, value| 就是典型例子。
Proc:把代码存进变量
Proc 让 Block 变成一等公民:
double = Proc.new { |n| n * 2 }
double.call(5) # => 10
double.(5) # 等价写法
Proc 可以存入数组、放进 Hash、作为参数传递,是实现策略模式和回调注册的常用手段。
Lambda:更严格的 Proc
Lambda 用 lambda 或 -> 创建:
greet = ->(name) { "Hello, #{name}" }
greet.call("Ruby")
它与 Proc 有两个关键区别:
- 参数检查:Lambda 像普通方法一样校验参数个数,传错会抛
ArgumentError;Proc 对缺失参数静默赋nil。 - return 行为:Lambda 里的
return只返回 Lambda 自身;Proc 里的return会直接跳出外层方法——这是最常见的 bug 来源。
闭包与变量捕获
Block、Proc、Lambda 都是闭包:创建时捕获周围的局部变量,之后即使脱离原作用域依然能访问和修改:
def counter
count = 0
-> { count += 1 }
end
c = counter
c.call # => 1
c.call # => 2
Rails 的 before_action、RSpec 的 let、Sidekiq 的任务定义,背后都是闭包在起作用。
常见问题(FAQ)
Q:方法什么时候应该用 Block 而不是传 Proc 参数?
当回调只在方法执行期间使用一次时用 Block(符合 Ruby 惯例且性能略好);需要把回调存下来稍后触发(如事件订阅)时才显式接收 Proc/Lambda。
Q:&. 和 &: 跟 Block 有什么关系?
&: 是 Symbol#to_proc 的语法糖,users.map(&:name) 等价于 users.map { |u| u.name }——符号被转成了一个 Proc。&. 则是安全导航运算符,与 Block 无关,注意别混淆。
Q:Lambda 和箭头函数 -> 完全等价吗?
是的,->(x) { x } 就是 lambda { |x| x } 的简写,行为一致。带参数时推荐 ->(x) 写法,参数位置更清晰。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。