Assignments

An assignment expression specifies one or more values for one or more lvalues. lvalue is the term for something that can appear on the lefthand side of an assignment operator. (Values on the righthand side of an assignment operator are sometimes called rvalues by contrast.) Variables, constants, attributes, and array elements are lvalues in Ruby. The rules for and the meaning of assignment expressions are somewhat different for different kinds of lvalues, and each kind is described in detail in this section.

There are three different forms of assignment expressions in Ruby. Simple assignment involves one lvalue, the = operator, and one rvalue. For example:

x = 1     # Set the lvalue x to the value 1

Abbreviated assignment is a shorthand expression that updates the value of a variable by applying some other operation (such as addition) to the current value of the variable. Abbreviated assignment uses assignment operators like += and *= that combine binary operators with an equals sign:

x += 1    # Set the lvalue x to the value x + 1

Finally, parallel assignment is any assignment expression that has more than one lvalue or more than one rvalue. Here is a simple example:

x,y,z = 1,2,3   # Set x to 1, y to 2 and z to 3

Parallel assignment is more complicated when the number of lvalues is not the same as the number of rvalues or when there is an array on the right. Complete details follow.

The value of an assignment expression is the value (or an array of the values) assigned. Also, the assignment operator is “right-associative”—if multiple assignments appear in a single expression, they are evaluated from right to left. This means that the assignment can be chained to assign the same value to multiple variables:

x = y = 0   # Set x and y to 0

Note that this is not a case of parallel assignment—it is two simple assignments, chained together: y is assigned the value 0, and then x is assigned the value (also 0) of that first assignment.

Assigning to Variables

When we think of assignment, we usually think of variables, and indeed, these are the most common lvalues in assignment expressions. Recall that Ruby has four kinds of variables: local variables, global variables, instance variables, and class variables. These are distinguished from each other by the first character in the variable name. Assignment works the same for all four kinds of variables, so we do not need to distinguish between the types of variables here.

Keep in mind that the instance variables of Ruby’s objects are never visible outside of the object, and variable names are never qualified with an object name. Consider this assignment:

point.x, point.y = 1, 2

The lvalues in this expression are not variables; they are attributes, and are explained shortly.

Assignment to a variable works as you would expect: the variable is simply set to the specified value. The only wrinkle has to do with variable declaration and an ambiguity between local variable names and method names. Ruby has no syntax to explicitly declare a variable: variables simply come into existence when they are assigned. Also, local variable names and method names look the same—there is no prefix like $ to distinguish them. Thus, a simple expression such as x could refer to a local variable named x or a method of self named x. To resolve this ambiguity, Ruby treats an identifier as a local variable if it has seen any previous assignment to the variable. It does this even if that assignment was never executed. The following code demonstrates:

class Ambiguous
  def x; 1; end # A method named "x". Always returns 1

  def test
    puts x      # No variable has been seen; refers to method above: prints 1

    # The line below is never evaluated, because of the "if false" clause. But
    # the parser sees it and treats x as a variable for the rest of the method.
    x = 0 if false

    puts x    # x is a variable, but has never been assigned to: prints nil

    x = 2     # This assignment does get evaluated
    puts x    # So now this line prints 2
  end
end

Assigning to Constants

Constants are different from variables in an obvious way: their values are intended to remain constant throughout the execution of a program. Therefore, there are some special rules for assignment to constants:

  • Assignment to a constant that already exists causes Ruby to issue a warning. Ruby does execute the assignment, however, which means that constants are not really constant.

  • Assignment to constants is not allowed within the body of a method. Ruby assumes that methods are intended to be invoked more than once; if you could assign to a constant in a method, that method would issue warnings on every invocation after the first. So, this is simply not allowed.

Unlike variables, constants do not come into existence until the Ruby interpreter actually executes the assignment expression. A nonevaluated expression like the following does not create a constant:

N = 100 if false

Note that this means a constant is never in an uninitialized state. If a constant exists, then it has a value assigned to it. A constant will only have the value nil if that is actually the value it was given.

Assigning to Attributes and Array Elements

Assignment to an attribute or array element is actually Ruby shorthand for method invocation. Suppose an object o has a method named m=: the method name has an equals sign as its last character. Then o.m can be used as an lvalue in an assignment expression. Suppose, furthermore, that the value v is assigned:

o.m = v

The Ruby interpreter converts this assignment to the following method invocation:

o.m=(v)  # If we omit the parens and add a space, this looks like assignment!

That is, it passes the value v to the method m=. That method can do whatever it wants with the value. Typically, it will check that the value is of the desired type and within the desired range, and it will then store it in an instance variable of the object. Methods like m= are usually accompanied by a method m, which simply returns the value most recently passed to m=. We say that m= is a setter method and m is a getter method. When an object has this pair of methods, we say that it has an attribute m. Attributes are sometimes called “properties” in other languages. We’ll learn more about attributes in Ruby in Accessors and Attributes.

Assigning values to array elements is also done by method invocation. If an object o defines a method named []= (the method name is just those three punctuation characters) that expects two arguments, then the expression o[x] = y is actually executed as:

o.[]=(x,y)

If an object has a []= method that expects three arguments, then it can be indexed with two values between the square brackets. The following two expressions are equivalent in this case:

o[x,y] = z
o.[]=(x,y,z)

Abbreviated Assignment

Abbreviated assignment is a form of assignment that combines assignment with some other operation. It is used most commonly to increment variables:

x += 1

+= is not a real Ruby operator, and the expression above is simply an abbreviation for:

x = x + 1

Abbreviated assignment cannot be combined with parallel assignment: it only works when there is a single lvalue on the left and a single value on the right. It should not be used when the lvalue is a constant because it will reassign the constant and cause a warning. Abbreviated assignment can, however, be used when the lvalue is an attribute. The following two expressions are equivalent:

o.m += 1
o.m=(o.m()+1)

Abbreviated assignment even works when the lvalue is an array element. These two expressions are equivalent:

o[x] -= 2
o.[]=(x, o.[](x) - 2)

Note that this code uses -= instead of +=. As you might expect, the -= pseudooperator subtracts its rvalue from its lvalue.

In addition to += and -=, there are 11 other pseudooperators that can be used for abbreviated assignment. They are listed in Table 4-1. Note that these are not true operators themselves, they are simply shorthand for expressions that use other operators. The meanings of those other operators are described in detail later in this chapter. Also, as we’ll see later, many of these other operators are defined as methods. If a class defines a method named +, for example, then that changes the meaning of abbreviated assignment with += for all instances of that class.

Table 4-1. Abbreviated assignment pseudooperators

AssignmentExpansion
x += yx = x + y
x -= yx = x - y
x *= yx = x * y
x /= yx = x / y
x %= yx = x % y
x **= yx = x ** y
x &&= yx = x && y
x ||= yx = x || y
x &= yx = x & y
x |= yx = x | y
x ^= yx = x ^ y
x <<= yx = x << y
x >>= yx = x >> y

Parallel Assignment

Parallel assignment is any assignment expression that has more than one lvalue, more than one rvalue, or both. Multiple lvalues and multiple rvalues are separated from each other with commas. lvalues and rvalues may be prefixed with *, which is sometimes called the splat operator, though it is not a true operator. The meaning of * is explained later in this section.

Most parallel assignment expressions are straightforward, and it is obvious what they mean. There are some complicated cases, however, and the following subsections explain all the possibilities.

Same number of lvalues and rvalues

Parallel assignment is at its simplest when there are the same number of lvalues and rvalues:

x, y, z = 1, 2, 3   # x=1; y=2; z=3

In this case, the first rvalue is assigned to the first lvalue; the second rvalue is assigned to the second lvalue; and so on.

These assignments are effectively performed in parallel, not sequentially. For example, the following two lines are not the same:

x,y = y,x     # Parallel: swap the value of two variables
x = y; y = x  # Sequential: both variables have same value

One lvalue, multiple rvalues

When there is a single lvalue and more than one rvalue, Ruby creates an array to hold the rvalues and assigns that array to the lvalue:

x = 1, 2, 3      # x = [1,2,3]

You can place an * before the lvalue without changing the meaning or the return value of this assignment.

If you want to prevent the multiple rvalues from being combined into a single array, follow the lvalue with a comma. Even with no lvalue after that comma, this makes Ruby behave as if there were multiple lvalues:

x, = 1, 2, 3     # x = 1; other values are discarded

Multiple lvalues, single array rvalue

When there are multiple lvalues and only a single rvalue, Ruby attempts to expand the rvalue into a list of values to assign. If the rvalue is an array, Ruby expands the array so that each element becomes its own rvalue. If the rvalue is not an array but implements a to_ary method, Ruby invokes that method and then expands the array it returns:

x, y, z = [1, 2, 3]  # Same as x,y,z = 1,2,3

The parallel assignment has been transformed so that there are multiple lvalues and zero (if the expanded array was empty) or more rvalues. If the number of lvalues and rvalues are the same, then assignment occurs as described earlier in Same number of lvalues and rvalues. If the numbers are different, then assignment occurs as described next in Different numbers of lvalues and rvalues.

We can use the trailing-comma trick described above to transform an ordinary nonparallel assignment into a parallel assignment that automatically unpacks an array on the right:

x = [1,2]    # x becomes [1,2]: this is not parallel assignment
x, = [1,2]   # x becomes 1: the trailing comma makes it parallel

Different numbers of lvalues and rvalues

If there are more lvalues than rvalues, and no splat operator is involved, then the first rvalue is assigned to the first lvalue, the second rvalue is assigned to the second lvalue, and so on, until all the rvalues have been assigned. Next, each of the remaining lvalues is assigned nil, overwriting any existing value for that lvalue:

x, y, z = 1, 2  # x=1; y=2; z=nil

If there are more rvalues than lvalues, and no splat operator is involved, then rvalues are assigned—in order—to each of the lvalues, and the remaining rvalues are discarded:

x, y = 1, 2, 3 # x=1; y=2; 3 is not assigned anywhere

The splat operator

When an rvalue is preceded by an asterisk, it means that that value is an array (or an array-like object) and that its elements should each be rvalues. The array elements replace the array in the original rvalue list, and assignment proceeds as described above:

x, y, z = 1, *[2,3]  # Same as x,y,z = 1,2,3

In Ruby 1.8, a splat may only appear before the last rvalue in an assignment. In Ruby 1.9, the list of rvalues in a parallel assignment may have any number of splats, and they may appear at any position in the list. It is not legal, however, in either version of the language, to attempt a “double splat” on a nested array:

x,y = **[[1,2]]   # SyntaxError!

Array, range and hash rvalues can be splatted. In general, any rvalue that defines a to_a method can be prefixed with a splat. Any Enumerable object, including enumerators (see Enumerators) can be splatted, for example. When a splat is applied to an object that does not define a to_a method, no expansion is performed and the splat evaluates to the object itself.

When an lvalue is preceded by an asterisk, it means that all extra rvalues should be placed into an array and assigned to this lvalue. The value assigned to that lvalue is always an array, and it may have zero, one, or more elements:

x,*y = 1, 2, 3  # x=1; y=[2,3]
x,*y = 1, 2     # x=1; y=[2]
x,*y = 1        # x=1; y=[]

In Ruby 1.8, a splat may only precede the last lvalue in the list. In Ruby 1.9, the lefthand side of a parallel assignment may include one splat operator, but it may appear at any position in the list:

# Ruby 1.9 only
*x,y = 1, 2, 3  # x=[1,2]; y=3
*x,y = 1, 2     # x=[1]; y=2
*x,y = 1        # x=[]; y=1

Note that splats may appear on both sides of a parallel assignment expression:

x, y, *z = 1, *[2,3,4]  # x=1; y=2; z=[3,4].

Finally, recall that earlier we described two simple cases of parallel assignment in which there is a single lvalue or a single rvalue. Note that both of these cases behave as if there is a splat before the single lvalue or rvalue. Explicitly including a splat in these cases has no additional effect.

Parentheses in parallel assignment

One of the least-understood features of parallel assignment is that the lefthand side can use parentheses for “subassignment.” If a group of two or more lvalues is enclosed in parentheses, then it is initially treated as a single lvalue. Once the corresponding rvalue has been determined, the rules of parallel assignment are applied recursively—that rvalue is assigned to the group of lvalues that was in parentheses. Consider the following assignment:

x,(y,z) = a, b

This is effectively two assignments executed at the same time:

x = a
y,z = b

But note that the second assignment is itself a parallel assignment. Because we used parentheses on the lefthand side, a recursive parallel assignment is performed. In order for it to work, b must be a splattable object such as an array or enumerator.

Here are some concrete examples that should make this clearer. Note that parentheses on the left act to “unpack” one level of nested array on the right:

x,y,z = 1,[2,3]             # No parens: x=1;y=[2,3];z=nil
x,(y,z) = 1,[2,3]           # Parens: x=1;y=2;z=3

a,b,c,d = [1,[2,[3,4]]]     # No parens: a=1;b=[2,[3,4]];c=d=nil
a,(b,(c,d)) = [1,[2,[3,4]]] # Parens: a=1;b=2;c=3;d=4

The value of parallel assignment

The return value of a parallel assignment expression is the array of rvalues (after being augmented by any splat operators).

Get The Ruby Programming Language now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.