Lesson 2.20: Pass by Value vs. Pass by Reference Flashcards
object
a bit of data that has some sort of state – sometimes called a value – and an associated behavior. It can be simple, like the boolean object true, or it can be complex, like an object that represents a database connection.
object_id
Every object in Ruby has a unique object id, and that object id can be retrieved simply by calling `#object_id` on the object in question. Even literals, such as numbers, booleans, `nil`, and Strings have object ids: >> 5.object_id => 11 >> true.object_id => 20 >> n nil.object_id => 8 >> "abc".object_id => 70101471581080
setter method
(or simply, a setter) is a method defined by a Ruby object that allows a programmer to explicitly change the value of part of an object. Setters always use a name like something=
Ex.: Array#[]=
Some languages make copies of method arguments, and pass those copies to the method — since they are merely copies, the original objects can’t be mutated. Objects passed to methods in this way are said to be ______, and the language is said to be using a ______ object passing strategy.
passed by value, pass by value
Other languages pass _______ to the method instead — a _______ can be used to mutate the original object, provided that object is mutable. Objects passed to methods in this way are said to be _________, and the language is said to be using a ________ object passing strategy.
references; reference; passed by reference; pass by reference
_______, as you’ll recall, means copying the original objects, so the original object cannot be mutated. Since immutable objects cannot be changed, they act like Ruby ______________ .
Pass by value; passes them around by value
Mutable objects, on the other hand, can always be mutated simply by calling one of their mutating methods. They act like Ruby passes them around by ________ ; it isn’t necessary for a method to mutate an object that is _____________, only that it can mutate the object. As you’ll recall, _________ means that only a _________ to an object is passed around; the variables used inside a method are bound to the original objects. This means that the method is free to mutate those objects. Once again, this isn’t completely accurate, but it is helpful.
reference; passed by reference; pass by reference; reference
The object that may or may not be mutated is of concern when discussing whether a method is mutating or non-mutating. For example, the method String#sub! is _______ with respect to the String used to call it, but _______ with respect to its arguments.
mutating; non-mutating
All methods are _______ with respect to immutable objects.
non-mutating
def fix(value) value.upcase! value.concat('!') value end s = 'hello' t = fix(s)
When this code runs, what values do s and t have?
We start by passing s to fix; this binds the String represented by ‘hello’ to value. In addition, s and value are now aliases for the String.
Next, we call #upcase! which converts the String to uppercase. A new String is not created; the String that is referenced by both s and value now contains the value ‘HELLO’.
We then call #concat on value, which also mutates value instead of creating a new String; the String now has a value of “HELLO!”, and both s and value reference that object.
Finally, we return a reference to the String and store it in t.
The only place we create a new String in this code is when we assign ‘hello to s. The rest of the time, we operate directly on the object, mutating it as needed. Thus, both s and t reference the same String, and that String has the value ‘HELLO!’
def fix(value) value = value.upcase value.concat('!') end s = 'hello' t = fix(s)
Now what happens to the values of s and t?
In this modified code, we assign the return value of value.upcase back to value. Unlike #upcase!, #upcase doesn’t mutate the String referenced by value; instead, it creates a new copy of the String referenced by value, mutates the new copy, and then returns a reference to the copy. We then bind value to the returned reference. s and t now reference different objects, and the String referenced by s still references its original value.
>> def fix(value) -- value = value.upcase -- value.concat('!') -- end => :fix >> s = 'hello' => "hello" >> s.object_id => 70349169469400 >> t = fix(s) => "HELLO!" >> s => "hello" >> t => "HELLO!" >> s.object_id => 70349169469400 >> t.object_id => 70349169435840
def fix(value) puts "initial object #{value.object_id}" value = value.upcase puts "upcased object #{value.object_id}" value.concat('!') end s = 'hello' puts "original object #{s.object_id}" t = fix(s) puts "final object #{t.object_id}"
If you run this code, you will see something like this: original object 70349169469400 initial object 70349169469400 upcased object 70349169435840 final object 70349169435840
This shows that value = value.upcase bound the return value of value.upcase to value; value now references a different object than it did before. Prior to the assignment, value referenced the same String as referenced by s, but after the assignment, value references a completely new String; the String referenced by #upcase’s return value.
Assignment ____ binds the target variable on the left hand side of the = to the object referenced by the right hand side. The object originally referenced by the target variable is _____ mutated.
always; never
def fix(value) value << 'xyz' value = value.upcase value.concat('!') end s = 'hello' t = fix(s)
This program mutates the original string so its value is helloxyz. However, thanks to the assignment on line 3, it is not mutated to HELLOXYZ or HELLOXYZ!; those mutations are made to the (different) object that the method returns.
>> s = 'Hello' => "Hello" >> s.object_id => 70101471465440 >> s += ' World' => "Hello World" >> s => "Hello World" >> s.object_id => 70101474966820
Though it looks as though we are mutating s when we write s += ‘ World’, we are actually creating a brand-new String with a new object id, then binding s to that new object.