When does variables in Ruby determine whether to hold a new reference? - ruby

I learned that in Ruby, variables hold references to objects, not the objects themselves.
For example:
a = "Tim"
b = a
a[0] = 'J'
Then a and b both have value "Jim".
However if I change the 3rd line to
a = "Jim"
Then a == Jim and b == Tim
I assume that means the code I changed created a new reference for a.
So why does changing a letter or changing the entire string make so much difference?
Follow-up question: Does Java work the same way?
Thank you.

The single thing to learn here is the difference between assignment and method call.
a = 'Jim'
is an assignment. You create a new string object (literal 'Jim') and assign it to variable a.
On the other side,
a[0] = 'J'
is a method call on an object already referenced by the variable a. A method call can't replace the object referenced by the variable with another one, it can just change the internal state of the object, and/or return another object.

I find that things like this are easiest to figure out using IRB:
>> a = 'Tim'
=> "Tim"
>> a.object_id
=> 2156046480
>> b = a
=> "Tim"
>> b.object_id
=> 2156046480
>> a.object_id == b.object_id
=> true
As you can see a and b have the same object_id, meaning they reference the same object. So when you change one, you change the other. Now assign something new to a:
>> a = 'Jim'
=> "Jim"
>> a.object_id
=> 2156019520
>> b.object_id
=> 2156046480
>> a.object_id == b.object_id
=> false
You made a point to a new object, while b still kept the old reference. Changing either of them now will not change the other one.

When you do a[0] = 'J', you're asking
Change the first character of the object referenced by a (which happens to be the same as b) to 'J'
While when you do a = "Jim", you're assigning an entirely new object reference (the string "Jim") to a. b is unaffected because you're not changing anything in the original reference.

Related

Ruby assign one value to two variables in one line

I'm in the process of building my own webserver and want to make a logger. After server get a message, I want both to log it and send the message to the parsers. I need to make some moditications to the message for logging (eg remove the password), but when I change second variable, first is changed too!
msg = log_msg = JSON.parse(something)
log_msg[:password] = '[FILTERED]'
raise msg.inspect # => {..., :password => '[FILTERED]'}
How can I avoid this behavior of my code?
UPDATED It seems more strange, because of irb:
2.2.1 :001 > a = b = 1
=> 1
2.2.1 :002 > b = 2
=> 2
2.2.1 :003 > b
=> 2
2.2.1 :004 > a
=> 1
After the assignment, msg and log_msg reference to the same object. If this is not what you expected, try this:
log_msg = JSON.parse(something)
msg = log_msg.dup
Note that the other example behave differently because Fixnum is special. From the manual:
Fixnum objects have immediate value. This means that when they are assigned or passed as parameters, the actual object is passed, rather than a reference to that object.
This question is tightly linked to the following duscussions:
Object assignment in ruby
Does ruby pass by reference or by value
Ruby parameters by reference or by value
Please read them carefully to understand what's going on (this addressed your code snippet with assigning integer values aswell).
To assign by value you could clone or dup methods. Check the value of object_id to realize if you're working on the same object or not.
a = {} # => {}
b = a # => {}
b.object_id # => 114493940
a.object_id # => 114493940
b = a.clone # => {}
b.object_id # => 115158164
a.object_id # => 114493940

Ruby variables referencing other variables

I have two variables, one (b) that references the other (a). When I modify a with a method, b is also modified:
a = "TEXT"
b = a
print b
#=> TEXT
a.downcase!
print b
#=> text
However, when I modify a directly, b retains its value:
a = "TEXT"
b = a
print b
#=> TEXT
a = "Something Else"
print b
#=> TEXT
Why is the behavior of b different when the variable it initially referenced is modified directly as opposed to by a method?
Is this an improper thing to do in Ruby, and if so, what would a better practice be for referencing one variable with another?
Ruby works with references, and you are making a little mistake in there.
This:
a.downcase!
as the 'bang' method suggests, is changing the value referenced by a.
So a is still the referencing the same object, which was just changed by the downcase! method
But this:
a = "Something Else"
is actually saying to a to reference a new object which happens to also be a string.
Since b was referencing another object and that object didn't changed, it still prints TEXT.
You can use the object_id to see what is going on here.
a = "text"
a.object_id
=> 70200807828580
b = a
b.object_id
=> 70200807828580 # b points to the same object that a does.
a = "new"
a.object_id
=> 70200807766420 # a now points to a new object
b.object_id
=> 70200807828580 # b still points to the original object.
So you see that the variable actually doesn't store the object itself. Instead it stores the id of the object. That's why if you copy an object you usually just copy the id of it rather than creating a whole new object.

Ruby assignment behavior

please help find some article of the next behavior.
a = 'qwer'
a = b
b << 'ty'
puts b # => 'qwerty'
puts a # => 'qwerty'
but if
a = 'qwer'
a = b
b = 'ty'
puts b # => 'ty'
puts a # => 'qwer'
I know why in this case
I know that it works well, but I can not find an explanation - why so
P.S.
if applicable - please give the links to the articles on this subject (or similar Maybe i miss more interesting feature like this).
Thn.
When you do
a = b
you make variable a keep reference to the same object as variable b. That's why when you type:
b << 'ty'
string contained in variable a will also change - this is the same String instance.
On the other hand, let's say you have variable b containing reference to string 'qwer'.
If you have:
a = b
b = 'ty'
in first line you assign variable a to the same object as b. In the second line, you assign a new String object to variable b. So in the end both variables have references to different objects.

What is the scope of a ruby string?

This is what I tried:
a = "Hello world"
a.object_id # => -633222538
b = a
b.object_id # => -633222538
b << " i say" # => "Hello world i say"
a # => "Hello world i say"
Why is it that both the variables b and a have the same object id? Also, when I change b, how did a also change?
Update:
How about when the variable is passed as an argument to a method? Why is the receiving variable having the same reference?
They are referencing the same object:
a = "Hello world" # a now references #-633222538
b = a # b now references #-633222538, too
b << " i say" # this appends " i say" to #-633222538
a # a still references #-633222538
String#<< doesn't assign a new object, it appends to the given string, thus changing the receiver.
I you want a copy, you can use clone or dup:
b = a.clone
a == b #=> true (same string values)
a.equal? b #=> false (different objects)
Regarding integers
There's no difference in referencing:
a = 100
a.object_id #=> 201
b = a
b.object_id #=> 201
Now both, a and b reference the same object. The only difference is that an integer cannot be changed in Ruby, they are fixed.
Passing variables as arguments
Again, the reference is passed:
a = "foo"
p = proc { |x| x << "bar" }
p.call(a)
a
#=> "foobar"
ENTER REFERENCES
The answer is that variables in Ruby (with a few exceptions, most notably variables bound to integers) don’t hold object values. a doesn’t contain "Hello world". Rather, a contains a reference to a string object. It’s the string object that has the characteristic of containing the letters that make up "Hello World".
In an assignment with a variable name on the left and an object on the right, the variable receives a reference to the object. In an assignment from one variable to another (a = b), the variable on the left receives a copy of the reference stored in the variable on the right, with the result that both variables now contain references to the same object.
The fact that variables hold references to objects has implications for operations that change objects. The string-concat operation
b << " i say"
concats the characters of the string to which b is a reference with the text " i say". The variable a contains another reference to the same string object. Even though the replace message goes to b, it causes a change to the object to which the reference in b refers. When you print out a, you see the result: the contents of the string have changed.
Some objects in Ruby are stored in variables as immediate values. These include in- tegers, symbols (which look like :this), and the special objects true, false, and nil. When you assign one of these values to a variable (x = 1), the variable holds the value itself, rather than a reference to it.
Copied and modified from Manning The Well Grounded Rubyist.
Looks like you called a mutable function on a variable which shared the same object with another variable. if you instead did b = b + 'i say' a would be left unchanged.
The variables a and b are references to a String object. When you did the b = a assignment, you copied the reference. It doesn't make a new copy of the object. If you want to copy the string object into a new object, you might do something like this:
a = "abc"
b = "" [or, b = String.new]
b << a
Now a and b will be different, independent string objects with the value "abc".

Both dup and clone return different objects, but modifying them alters the original object

I have an array of values that I use as a reference for order when I'm printing out hash values. I'd like to modify the array so that the array values are "prettier". I figured I'd just dup or clone the array, change the values and the original object would remain unchanaged. However (in irb)...
#arr = ['stuff', 'things']
a = #arr.clone
b = #arr.dup
So, at the very least, a and #arr are different objects:
a.object_id == #arr.object_id
=> false
But now it gets strange
a[0].capitalize!
a
=> ['Stuff', 'things']
#arr
=> ['Stuff', 'things'] ##<-what?
b
=> ['Stuff', 'things']## <-what???
ok... so modifying one changes the others, lets change it back?
a[0] = 'stuff'
a
=> ['stuff', 'things']
#arr
=> ['Stuff', 'things'] ## <- WHAT?????
For completeness b[1].capitalize! has the same effect, capitalizing all three array's second position
So... does the bang on the end of capitalize make it extra potent? Enough to cross over to other objects?? I know of other ways of doing this, but this just seemed extremely odd to me. I assume this has something to do with being a "shallow copy". Suggestions on the best way to do this?
dupand clone make new instances of the arrays, but not of the content, it is no deep copy.
See:
array0 = ['stuff', 'things']
array1 = array0.clone
array2 = array0.dup
puts "Array-Ids"
p array0.object_id
p array1.object_id
p array2.object_id
puts "Object ids"
array0.each_with_index{|_,i|
p array0[i].object_id
p array1[i].object_id
p array2[i].object_id
p '--------'
}
The elements inside the array share the same object_id - they are the same object. The arrays have different object ids.
When you a[0].capitalize! you modify an object, which is part in three different arrays.
See also
Duplicating a Ruby array of strings
Deep copy of arrays in Ruby
How to create a deep copy of an object in Ruby?

Resources