Changing value of ruby variables/references

12,082

Solution 1

my_var_a and my_var_set are different references, but they point at the same object. If you modify the object in my_var_set, the change shows up in my_var_a. However, if you repoint my_var_set at a new object, that doesn't change what my_var_a points at.

Edit: clarification...

What Ruby does is called passing references by value. When you say

my_var_a = "nothing happend to me"

Ruby saves the string "nothing happend to me" in a memory location (let's call it 1000), and saves the my_var_a reference in another memory location (let's say 2000). When your code uses my_var_a, the interpreter looks at location 2000, see that it points to 1000, then gets the actual string value from 1000.

When you call parse_set(my_var_a), Ruby actually creates a new reference named my_var_set and points it to the string that my_var_a was pointing at (memory location 1000). However, my_var_set is a copy of the my_var_a reference -- let's say my_var_set was created at memory location 3000. my_var_a and my_var_set are 2 completely different references in memory, they just happen to point at the same exact memory location which holds the string value.

The statement my_var_set = "my value changed" in parse_set creates a new string in memory and points my_var_set at that new memory location. However, this doesn't change what the original my_var_a reference points at! Now that my_var_set points at a different memory location, nothing that you do to that variable will affect my_var_a.

The same reference copy happens for parse_sub as well. But the reason that parse_sub changes the string is because you're calling a method directly on the my_var_sub reference. When you do this, the interpreter gets the object that my_var_sub is pointing at and then modifies it. So that change will show up in the my_var_a reference, because it still points at the same string.

Solution 2

irb(main):008:0* a = 'abc'
=> "abc"
irb(main):009:0> a.replace('def')
=> "def"
irb(main):010:0> a
=> "def"

I've had to use replace exactly zero times in all the years I've been programming in Ruby. I wonder if there's a better design for your code.

Most string methods which change the receiver are suffixed by ! (sub!, capitalize!, chomp!, etc.) Some that change the receiver are not suffixed by ! (replace is one). If you call a method that changes the receiver, any and all references to that object will see the change. if you call a method that does not change receiver, the method returns a new string.

gsub does not modify the receiver, but instead returns a new instance of String:

a = "foo"
b = a
p a.sub(/o/, '#')     # "f##"
p a                   # => "foo"
p b                   # => "foo"

gsub! does modify the receiver:

a = "foo"
b = a
p a.sub!(/o/, '#')    # => "f#o"
p a                   # => "f#o"
p b                   # => "f#o"
Share:
12,082
nocksock
Author by

nocksock

Updated on June 06, 2022

Comments

  • nocksock
    nocksock almost 2 years

    I just stumbled upon something i don't quite understand. I know that variables in ruby are references. So that awesome stuff is possible. But when i pass a variable to a method, it behaves strangely:

    my_var_a = "nothing happend to me"
    my_var_b = "nothing happend to me"
    
    def parse_set(my_var_set)
      my_var_set = "my value changed"
    end
    
    def parse_sub(my_var_sub)
      my_var_sub.sub! /(.*)/, "my value changed"
    end
    
    parse_set(my_var_a)
    parse_sub(my_var_b)
    
    my_var_a # => "nothing happend to me"
    my_var_b # => "my value changed"
    

    Can you explain to me why it works with sub! and = leaves the object unchanged? How can I avoid to use sub! but having the same result?

  • nocksock
    nocksock over 14 years
    Can you somehow rephrase that? I don't really get the part about repointing :) (also i changed the variable names for less ambiguity) And how can i change the value without using a ridiculous sub! and regex?
  • Kaleb Brasee
    Kaleb Brasee over 14 years
    I added a little more information, hope that makes things clearer. Passing reference by value is complicated, and takes a while to realize what's going on. Also, to change the value without using sub, just have your parse method return a value, and assign it to the original variable: my_var_a = some_parse_method(my_var_a).
  • nocksock
    nocksock over 14 years
    Wow, that is a really good and simple explanation. Thank you!
  • nocksock
    nocksock over 14 years
    Thank you for pointing out the replace method. But the answer of Kaleb explained to me the internals so I could understand what's going on, otherwise I would've accepted your answer :D
  • Wayne Conrad
    Wayne Conrad almost 8 years
    "Pass reference by value" is the most accurate way to describe how Ruby works.