Destructuring assignment in Ruby
My post on underscores in Ruby attracted quite a lot of interest, particularly on the topic of destructuring assignment, so I thought I’d go into a bit more detail.
As mentioned previously, Ruby supports destructuring assignment. You can assign an array to multiple variables both in direct assignment:
a, b, c = [1, 2, 3]
and in block parameters:
[[1, 2, 3]].map { |a, b, c| "..." }
I’ll explain everything with the first form from here on, as it’s a little simpler. Unless I’ve missed something, all these forms should work equally well in both forms; let me know if you spot something that doesn’t. I’ll only be talking about Ruby 1.9, too, as Ruby 1.8 doesn’t support all of these syntactic constructs.
The outermost square brackets are optional when assigning variables, but I’ll use them throughout as I think it’s a little clearer in this context.
Multi-level destructuring
You can parenthesise any set of variables on the left hand side to mirror the structure on the right:
a, (b, c) = [1, [2, [3, 4]]]
a # => 1
b # => 2
c # => [3, 4]
but you don’t have to stop there. It’s turtles all the way down:
a, (b, (c, d)) = [1, [2, [3, 4]]]
a # => 1
b # => 2
c # => 3
d # => 4
‘Splatting’: assigning multiple elements
Extra values on the right hand side are usually discarded:
a, b, c = [1, 2, 3, 4, 5]
a # => 1
b # => 2
c # => 3
Prepending an asterisk—the ‘splat’ operator—to a variable tells it to gather up all the unassigned elements:
a, b, *c = [1, 2, 3, 4, 5]
a # => 1
b # => 2
c # => [3, 4, 5]
This works at the beginning of the list, too:
*a, b, c = [1, 2, 3, 4, 5]
a # => [1, 2, 3]
b # => 4
c # => 5
or in the middle:
a, *b, c = [1, 2, 3, 4, 5]
a # => 1
b # => [2, 3, 4]
c # => 5
But you can only use it once:
a, *b, *c = [1, 2, 3, 4, 5]
# stdin:81: syntax error, unexpected tSTAR
# a, *b, *c = [1, 2, 3, 4, 5]
# ^
That is, you can only use it once at a given level. Each level of parentheses can have a splat:
*a, (b, *c) = [1, 2, [3, 4, 5]]
a # => [1, 2]
b # => 3
c # => [4, 5]
Ignoring elements
You can reuse an underscore to represent any element you don’t care about:
a, _, b, _, c = [1, 2, 3, 4, 5]
a # => 1
b # => 3
c # => 5
To ignore multiple elements, use a single asterisk—I’m going to call it a ‘naked splat’ for no better reason than that it sounds a bit amusing:
a, *, b = [1, 2, 3, 4, 5]
a # => 1
b # => 5
The same rules apply to naked splats as to splatted variables: you can only use one in any given level of parentheses, but you can reuse it at each level:
a, *, (*, b, c) = [1, 2, 3, [4, 5, 6, 7]]
a # => 1
b # => 6
c # => 7
I wouldn’t necessarily encourage you to leap straight out and start using destructuring assignment everywhere, but it has its place. Use it where it makes your code clearer, and remember: if you need to be compatible with Ruby 1.8, much of this won’t work.