Any array can be quickly decomposed by assigning its elements into multiple variables. A simple example:
arr = [1, 2, 3]
# ---
a = arr[0]
b = arr[1]
c = arr[2]
# --- or, the same
a, b, c = arr
Preceding a variable with the splat operator (*
) puts into it an array of all the elements that haven't been captured by other variables. If none are left, empty array is assigned. Only one splat can be used in a single assignment:
a, *b = arr # a = 1; b = [2, 3]
a, *b, c = arr # a = 1; b = [2]; c = 3
a, b, c, *d = arr # a = 1; b = 2; c = 3; d = []
a, *b, *c = arr # SyntaxError: unexpected *
Decomposition is safe and never raises errors. nil
s are assigned where there's not enough elements, matching the behavior of []
operator when accessing an index out of bounds:
arr[9000] # => nil
a, b, c, d = arr # a = 1; b = 2; c = 3; d = nil
Decomposition tries to call to_ary
implicitly on the object being assigned. By implementing this method in your type you get the ability to decompose it:
class Foo
def to_ary
[1, 2]
end
end
a, b = Foo.new # a = 1; b = 2
If the object being decomposed doesn't respond_to?
to_ary
, it's treated as a single-element array:
1.respond_to?(:to_ary) # => false
a, b = 1 # a = 1; b = nil
Decomposition can also be nested by using a ()
-delimited decomposition expression in place of what otherwise would be a single element:
arr = [1, [2, 3, 4], 5, 6]
a, (b, *c), *d = arr # a = 1; b = 2; c = [3, 4]; d = [5, 6]
# ^^^^^
This is effectively the opposite of splat.
Actually, any decomposition expression can be delimited by ()
. But for the first level decomposition is optional.
a, b = [1, 2]
(a, b) = [1, 2] # the same thing
Edge case: a single identifier cannot be used as a destructuring pattern, be it outer or a nested one:
(a) = [1] # SyntaxError
a, (b) = [1, [2]] # SyntaxError
When assigning an array literal to a destructuring expression, outer []
can be omitted:
a, b = [1, 2]
a, b = 1, 2 # exactly the same
This is known as parallel assignment, but it uses the same decomposition under the hood. This is particularly handy for exchanging variables' values without employing additional temporary variables:
t = a; a = b; b = t # an obvious way
a, b = b, a # an idiomatic way
(a, b) = [b, a] # ...and how it works
Values are captured when building the right-hand side of the assignment, so using the same variables as source and destination is relatively safe.