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 有两个关键区别:

  1. 参数检查:Lambda 像普通方法一样校验参数个数,传错会抛 ArgumentError;Proc 对缺失参数静默赋 nil。
  2. 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) 写法,参数位置更清晰。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台