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