In Ruby 3.4 and later, how does the implicit it block parameter differ from the numbered parameter _1, and where is each rejected?
answer
- _1 since 2.7, it since 3.4
- only in parameterless blocks
- never mix it and _N in one block
- it nests; _1 does not
- it is a soft keyword
basics
~20 sBoth name a block's first argument without declaring it. it (Ruby 3.4) nests freely and is only a soft keyword; _1 (Ruby 2.7) comes with _2 to _9, cannot be used in nested numbered blocks and is reserved.
solid answer
~50 s`_1` to `_9` arrived in Ruby 2.7 and name a block's parameters by position, so `pairs.map { _1 + _2 }` destructures each pair. `it`, added in Ruby 3.4, names the single parameter: `(1..5).map { it**2 }`. Both need a block with no ordinary parameter list: `{ |x| _1 }` and even `{ |; tmp| _1 }` are `SyntaxError`s, and `it` is rejected there too unless a local named `it` exists. One block cannot mix them. The differences: `it` works in nested blocks, each referring to its own block, while an inner block cannot use numbered parameters when the outer one does. `it` is a soft keyword, so `it = 5` still defines a local, whereas `_1` is reserved and assigning it is a `SyntaxError`. A block using only `it` or only `_1` takes one argument and does not destructure an array.
code
ruby · 12 lines%w[ab cd].each { it.each_char { p it } } # prints "a", "b", "c", "d"
{a: 1, b: 2}.map { it } # => [[:a, 1], [:b, 2]]
{a: 1, b: 2}.map { "#{_1}=#{_2}" } # => ["a=1", "b=2"]
it = 5 # fine: it is a soft keyword
# Each line below is a SyntaxError:
# [1, 2].map { |x| it }
# [1, 2].map { _1 + it }
# %w[ab cd].each { _1.each_char { p _1 } }
# _1 = 3go deeper
Recall that it and _1 name a block's argument without declaring it, and that they only work in blocks without explicit parameters.
Explain that _2 and beyond destructure, that a lone it or _1 does not, and that one block cannot mix the two styles.
Explain the nesting rule, the soft-keyword status of it versus the reserved _1, and when named parameters serve readability better on Ruby 4.0 code.
Set a team convention for anonymous block parameters across Ruby versions still in use, weighing concise one-liners against readable, reviewable blocks.
## Two kinds of anonymous block parameter Short blocks often repeat a throwaway name: `users.map { |u| u.email }`. Ruby offers two **anonymous block parameters** to drop it: - **Numbered parameters** `_1` to `_9`, added in **Ruby 2.7**, name arguments by position. - **`it`**, added in **Ruby 3.4**, names the single argument. Ruby 3.3 prepared the change by deprecating a bare `it` call in a block without parameters. ```ruby (1..5).map { it**2 } # => [1, 4, 9, 16, 25] (1..5).map { _1**2 } # => [1, 4, 9, 16, 25] [[1, 2], [3, 4]].map { _1 + _2 } # => [3, 7] ``` ## Rules they share 1. **Parameterless blocks only.** If the block declares anything between the pipes, both are rejected at parse time. `{ |x| it }`, `{ |x| _1 }`, `{ || _1 }` and `{ |; tmp| it }` all raise `SyntaxError` with a message that an ordinary parameter is defined. The one twist: if a local variable named `it` is already in scope, `{ |x| it }` parses and simply reads that variable. 2. **One style per block.** `{ _1 + it }` and `{ it + _2 }` are `SyntaxError`s; a block uses either `it` or numbered parameters. 3. **Arity.** A block using `it` behaves as if it declared one parameter; a block using `_1` and `_2` behaves as if it declared two. ## Where they differ | Aspect | `it` | `_1` ... `_9` | |---|---|---| | Since | Ruby 3.4 | Ruby 2.7 | | Several parameters | no, one only | yes, up to `_9` | | Nested blocks | allowed, each block has its own `it` | inner block cannot use them if the outer does | | As a variable or method name | allowed (soft keyword) | reserved, `SyntaxError` | | Explicit `\|x\|` in same block | `SyntaxError` unless a local `it` exists | `SyntaxError` | **Nesting** is the difference interviewers probe. This works and prints each character: ```ruby %w[ab cd].each { it.each_char { p it } } ``` The numbered version, `%w[ab cd].each { _1.each_char { p _1 } }`, is rejected with "numbered parameter is already used in outer block". Mixing the two across levels is allowed: `1.then { [it, 2.then { _1 }] }` returns `[1, 2]`. ## Destructuring A block that uses only `it` or only `_1` takes **one** argument, so it does not split an array element: - `[[1, 2]].map { it }` and `[[1, 2]].map { _1 }` both return `[[1, 2]]`. - `{a: 1, b: 2}.map { it }` returns the pairs `[[:a, 1], [:b, 2]]`. - Referencing `_2` makes the block take two parameters, so `{a: 1}.map { "#{_1}=#{_2}" }` returns `["a=1"]`. ## it is a soft keyword `it` is not reserved, so existing code keeps working: - `it = 5` defines an ordinary local variable. - A method named `it`, as in test DSLs that write `it "does something" do ... end`, still works, because a call with arguments is a method call. - In a block with explicit parameters, `it` refers to a local variable named `it` when one is in scope, instead of raising. By contrast, `_1 = "x"` or `def x(_1)` is a `SyntaxError`, because the names `_1` to `_9` are reserved for numbered parameters. ## Common mistakes - **Adding a parameter later.** Turning `{ it.email }` into `{ |user| it.email }` during a refactor makes the parser reject the block; rename every `it` at the same time. - **Expecting destructuring from `it`.** Over a Hash, `it` is the `[key, value]` pair; use `_1`/`_2` or explicit `|key, value|` to split it. - **Nesting numbered parameters.** Moving a `_1` block inside another `_1` block fails to parse; switch one level to `it` or to a named parameter. - **Supporting older Rubies.** On Ruby 3.3 and earlier, `it` inside a block is not the block's argument, so gems that support those versions should keep `_1` or named parameters. ## Choosing between them - Prefer **`it`** for one-argument, one-line blocks on Ruby 3.4+: it reads as English and nests. - Use **`_1`, `_2`** only for short blocks that genuinely need two positions. - Use **named parameters** once a block spans lines or its meaning is not obvious; names document intent. - Code that must run on Ruby 3.3 or older cannot rely on `it` as a parameter.
- What did Ruby 3.3 do with a bare it inside a block without parameters?It still treated a bare `it` as a method call but deprecated that form, announcing that `it` would refer to the first block parameter in Ruby 3.4. That gave code bases one release to rename helpers before the meaning changed.
- Can a lambda literal use it, and does -> () { it } behave the same as -> { it }?A stabby lambda without a parameter list can use `it`: `-> { it * 2 }.call(3)` returns 6. Writing empty parentheses, `->() { it }`, declares an ordinary (empty) parameter list, so the parser rejects `it` there with a `SyntaxError`.
saying these in an interview costs you the question
- it and _1 can be mixed in the same block
- it is a reserved word, so a local variable named it breaks
- Numbered parameters can be reused in a nested block
- A block using only _1 splats an array element into its parts
- it still names the first argument when the block also declares |x|