rubyguides

Kernel#Array

Array(object) -> array

Kernel#Array is the Kernel-provided functional converter that turns a value into an Array. It follows the same pattern as String(), Integer(), and Float(): a private method on Kernel that you call as a bare function. The interesting part is the dispatch order, because that is what makes hashes turn into pairs, sets unwrap into elements, and strings wrap into a single-element array.

Signature

The signature:

Array(object) -> array

The method takes exactly one argument. It has no exception: keyword (unlike Integer() and Float()) and no block form. Calling it with zero or more than one argument raises ArgumentError:

Array()        # => raises ArgumentError: wrong number of arguments (given 0, expected 1)
Array(1, 2)    # => raises ArgumentError: wrong number of arguments (given 2, expected 1)

Because Array is a private instance method on Kernel exposed as a module function, you call it bare (Array([1])) rather than on a receiver.

What it does

Array() walks a fixed dispatch to convert its argument:

  1. If the argument is nil, return [] immediately. The methods are never checked.
  2. Otherwise, if the argument responds to to_ary, call it. The return value must be an Array, or TypeError is raised.
  3. Otherwise, if the argument responds to to_a, call it. The return value must be an Array, or TypeError is raised.
  4. Otherwise, wrap the argument in a one-element array: [argument].

The to_ary step is the “implicit” conversion hook (the object saying “substitute me transparently”), and to_a is the “explicit” conversion hook (“I can produce an array on request”). Array() honors that order, which is the source of most of the surprises covered in the gotchas below. In MRI, the whole thing runs through the C function rb_f_array, which is a thin wrapper over rb_Array(arg).

Examples

Arrays and ranges

Array already implements to_ary (returning self), so arrays pass through unchanged. Ranges get unwrapped through Enumerable#to_a:

Array(nil)        # => []
Array([1, 2, 3])  # => [1, 2, 3]
Array([])         # => []
Array(1..3)       # => [1, 2, 3]
Array("a".."c")   # => ["a", "b", "c"]

Array() does not flatten. Array([1, [2, 3]]) is [1, [2, 3]]. Reach for Array(x).flatten when you need that.

Hashes and enumerables

Hash defines to_a, which returns an array of [key, value] pairs. That is what Array() ends up calling:

Array({ a: 1, b: 2 })   # => [[:a, 1], [:b, 2]]
Array({})               # => []
Array(Hash.new(0))      # => []

Other Enumerable sources behave the same way. Set is the exception: it defines to_ary (returning the elements), so it unwraps cleanly without going through the pair step:

require "set"
Array(Set[1, 2, 3])     # => [1, 2, 3]  to_ary branch
Array(1.upto(3))        # => [1, 2, 3]  to_a branch

Wrapping non-array values

Anything that defines neither to_ary nor to_a ends up in a one-element array. That is why Array("hello") is ["hello"], not ["h", "e", "l", "l", "o"]:

Array("hello")   # => ["hello"]
Array(42)        # => [42]
Array(3.14)      # => [3.14]
Array(:ok)       # => [:ok]
Array(true)      # => [true]
Array(false)     # => [false]
Array(/rx/)      # => [/rx/]
Array(Time.now)  # => [current Time instance]

To split a string into characters, use "abc".chars or "abc".split("").

The to_ary and to_a branches

If you implement one of the two conversion hooks, Array() will pick it up. A non-Array return from either hook is a TypeError:

class Box
  def to_ary
    [1, 2, 3]
  end
end
Array(Box.new)   # => [1, 2, 3]  to_ary wins

class Bad
  def to_ary
    "not an array"
  end
end
Array(Bad.new)
# => raises TypeError: can't convert Bad into Array (Bad#to_ary gives String)

The same contract holds for to_a, the explicit conversion hook used by Range, Hash, and most Enumerable subclasses when Array() reaches them. A to_a that returns anything other than an Array is still a TypeError, and the message names the offending class and method so you can find the bad implementation fast:

class Pair
  def to_a
    [:a, :b]
  end
end
Array(Pair.new)   # => [:a, :b]  no to_ary, falls through to to_a

class BadA
  def to_a
    42
  end
end
Array(BadA.new)
# => raises TypeError: can't convert BadA into Array (BadA#to_a gives Integer)

Array() vs to_a vs Array.wrap

Three ways to coerce a value to an array, and the choice matters for some inputs:

InputArray()Object#to_a (if defined)Array.wrap (Rails)
nil[][] (NilClass#to_a)[]
[1, 2, 3][1, 2, 3] (via to_ary)[1, 2, 3][1, 2, 3]
1..3[1, 2, 3] (via to_a)NoMethodError on Range[1, 2, 3]
{a: 1}[[:a, 1]] (via to_a)[[:a, 1]][[:a, 1]]
"abc"["abc"]NoMethodError on String["abc"]
42[42]NoMethodError[42]
Set[1, 2][1, 2] (via to_ary)[1, 2][1, 2]

Array() is the strict Kernel conversion that walks the standard protocol. Array.wrap (Rails) is a defensive helper that skips the to_ary step in some edge cases (notably when to_ary returns nil instead of an array) and is the safer pick inside Rails code.

Gotchas

Behaviors that catch people out at least once. The first three are the everyday ones; the rest are protocol and edge cases.

The everyday traps

  1. Array(nil) is [], not [nil]. Useful in defensive code (Array(opts[:tags])); also the single most common “wait, what?” moment.
  2. Array("abc") is ["abc"], not ["a", "b", "c"]. Strings implement neither to_a nor to_ary. Use "abc".chars or "abc".split("") to split into characters.
  3. Array({a: 1}) is [[:a, 1]]. Hashes implement to_a (returning pairs), not to_ary. If you wanted the keys, write Array(hash.keys).

Protocol and edge cases

  1. Open ranges do not work. Array(1..Float::INFINITY) raises because the range is not enumerable to a finite array. Use (1..n).to_a or 1.upto(n).to_a for explicit intent.
  2. A non-Array return from to_ary or to_a is a TypeError. Array() is a strict caller of the conversion protocol, so a buggy to_ary in a third-party class will surface here.
  3. Sets are unwrapped, hashes are pairified. Set[1, 2, 3] defines to_ary and returns [1, 2, 3]. {a: 1} defines to_a and returns [[:a, 1]]. Different conversion contracts, worth keeping straight.
  4. Frozen inputs are not duplicated. If you pass a frozen array, you get the same object back, not a copy. Array([1, 2, 3].freeze).frozen? is true. Call dup first if you need a fresh mutable array.
  5. No exception: keyword. Unlike Integer(), Float(), Complex(), and Rational(), there is no opt-in for “return nil on failure.” The standard workaround is Array(obj) rescue nil.
  6. Array() is private. You call it as a bare function. You cannot call it on an explicit receiver in the usual way, which is why Array() is so handy in DSLs and module_eval blocks.
  7. Array() does not recurse. It does not flatten nested arrays. Use Array(x).flatten or Array(x).flatten(1) for that.

Patterns in real code

A few small patterns that show up in production Ruby code. The first is the one most worth keeping in your head: Array() is the safe Ruby coercion for “give me an array of this, with nil handled”:

def tag_list(opts)
  Array(opts[:tags])   # nil -> [], Array -> itself, String -> [string], Range -> elements
end

def as_pairs(maybe_hash)
  Array(maybe_hash)    # nil -> [], Hash -> [[k, v], ...], other -> [other]
end

def flatten_input(maybe_nested)
  Array(maybe_nested).flatten   # never raises; flattens recursively
end

Two reminders about the conversion protocol that bit people the first time. Set defines to-ary and unwraps to its elements. Hash defines to_a and pairifies to [[k, v], ...]. The contracts are not symmetric, so pick the one that matches the input shape you actually expect:

require "set"
Array(Set[1, 2, 3])     # => [1, 2, 3]  via to-ary, returns the elements
Array({ a: 1, b: 2 })   # => [[:a, 1], [:b, 2]]  via to_a, returns pairs

If you want the defensive “give me a one-level array of this, period” behavior in Rails, prefer Array.wrap over Array(); it skips the to-ary step when to-ary returns nil and is the safer pick inside Rails code. Outside Rails, Array(obj) rescue nil is the standard idiom for the rare case where you want coercion to fail soft.

See also

  • Array class — the Array class itself, the destination of the conversion, not the function.
  • Kernel#String — sibling converter String(), with the same private-method-as-function shape.
  • Kernel#Float — sibling converter Float(), the contrast for the exception: keyword story.