Ruby Language Decomposition


Example

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. nils 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.