How does Ruby return two values?

Ruby

Ruby Problem Overview


Whenever I swap values in an array, I make sure I stored one of the values in a reference variable. But I found that Ruby can return two values as well as automatically swap two values. For example,

array = [1, 3, 5 , 6 ,7]
array[0], array[1] = array[1] , array[0] #=> [3, 1] 

I was wondering how Ruby does this.

Ruby Solutions


Solution 1 - Ruby

Unlike other languages, the return value of any method call in Ruby is always an object. This is possible because, like everything in Ruby, nil itself is an object.

There's three basic patterns you'll see. Returning no particular value:

def nothing
end

nothing
# => nil

Returning a singular value:

def single
  1
end

x = single
# => 1

This is in line with what you'd expect from other programming languages.

Things get a bit different when dealing with multiple return values. These need to be specified explicitly:

def multiple
  return 1, 2
end

x = multiple
# => [ 1, 2 ]
x
# => [ 1, 2 ]

When making a call that returns multiple values, you can break them out into independent variables:

x, y = multiple
# => [ 1, 2 ]
x
# => 1
y
# => 2

This strategy also works for the sorts of substitution you're talking about:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1

Solution 2 - Ruby

No, Ruby doesn't actually support returning two objects. (BTW: you return objects, not variables. More precisely, you return pointers to objects.)

It does, however, support parallel assignment. If you have more than one object on the right-hand side of an assignment, the objects are collected into an Array:

foo = 1, 2, 3
# is the same as
foo = [1, 2, 3]

If you have more than one "target" (variable or setter method) on the left-hand side of an assignment, the variables get bound to elements of an Array on the right-hand side:

a, b, c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary[2]

If the right-hand side is not an Array, it will be converted to one using the to_ary method

a, b, c = not_an_ary
# is the same as
ary = not_an_ary.to_ary
a = ary[0]
b = ary[1]
c = ary[2]

And if we put the two together, we get that

a, b, c = d, e, f
# is the same as
ary = [d, e, f]
a = ary[0]
b = ary[1]
c = ary[2]

Related to this is the splat operator on the left-hand side of an assignment. It means "take all the left-over elements of the Array on the right-hand side":

a, b, *c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary.drop(2) # i.e. the rest of the Array

And last but not least, parallel assignments can be nested using parentheses:

a, (b, c), d = ary
# is the same as
a = ary[0]
b, c = ary[1]
d = ary[2]
# which is the same as
a = ary[0]
b = ary[1][0]
c = ary[1][1]
d = ary[2]

When you return from a method or next or break from a block, Ruby will treat this kind-of like the right-hand side of an assignment, so

return 1, 2
next 1, 2
break 1, 2
# is the same as
return [1, 2]
next [1, 2]
break [1, 2]

By the way, this also works in parameter lists of methods and blocks (with methods being more strict and blocks less strict):

def foo(a, (b, c), d) p a, b, c, d end

bar {|a, (b, c), d| p a, b, c, d }

Blocks being "less strict" is for example what makes Hash#each work. It actually yields a single two-element Array of key and value to the block, but we usually write

some_hash.each {|k, v| }

instead of

some_hash.each {|(k, v)| }

Solution 3 - Ruby

tadman and Jörg W Mittag know Ruby better than me, and their answers are not wrong, but I don't think they are answering what OP wanted to know. I think that the question was not clear though. In my understanding, what OP wanted to ask has nothing to do with returning multiple values.


The real question is, when you want to switch the values of two variables a and b (or two positions in an array as in the original question), why is it not necessary to use a temporal variable temp like:

a, b = :foo, :bar
temp = a
a = b
b = temp

but can be done directly like:

a, b = :foo, :bar
a, b = b, a

The answer is that in multiple assignment, the whole right hand side is evaluated prior to assignment of the whole left hand side, and it is not done one by one. So a, b = b, a is not equivalent to a = b; b = a.

First evaluating the whole right hand side before assignment is a necessity that follows from adjustment when the both sides of = have different numbers of terms, and Jörg W Mittag's description may be indirectly related to that, but that is not the main issue.

Solution 4 - Ruby

Arrays are a good option if you have only a few values. If you want multiple return values without having to know (and be confused by) the order of results, an alternative would be to return a Hash that contains whatever named values you want.

e.g.

def make_hash
  x = 1
  y = 2
  {x: x, y: y}
end

hash = make_hash
# => {:x=>1, :y=>2}
hash[:x]
# => 1
hash[:y]
# => 2

Solution 5 - Ruby

Creating a hash as suggested by some is definitely better than array as array indexing can be confusing. When an additional attribute needs to be returned at a certain index, we'll need to make changes to all the places where the return value is used with array.

Another better way to do this is by using OpenStruct. Its advantage over using a hash is its ease of accessibility.

Example: computer = OpenStruct.new(ram: '4GB')

there are multiple ways to access the value of ram

  • as a symbol key: computer[:ram]
  • as a string key: computer['ram']
  • as an attribute(accessor method): computer.ram

Reference Article: https://medium.com/rubycademy/openstruct-in-ruby-ab6ba3aff9a4

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPeteView Question on Stackoverflow
Solution 1 - RubytadmanView Answer on Stackoverflow
Solution 2 - RubyJörg W MittagView Answer on Stackoverflow
Solution 3 - RubysawaView Answer on Stackoverflow
Solution 4 - RubypronoobView Answer on Stackoverflow
Solution 5 - RubyAakankshaView Answer on Stackoverflow