Search code examples
rubyruby-hash

Hash appears to be passing by reference : Ruby


I know Title is ambiguous, so lets look in to below example. I have a Hash named sh , then I assign sh to a new variable su , after that I am modifying sh , but su also getting modified . I want to keep su with original content.

irb(main):001:0> sh = {"name"=>"shan", "age"=>"33" , "sex"=>"male"}
=> {"name"=>"shan", "age"=>"33", "sex"=>"male"}
irb(main):002:0> su = sh
=> {"name"=>"shan", "age"=>"33", "sex"=>"male"}
irb(main):003:0> su
=> {"name"=>"shan", "age"=>"33", "sex"=>"male"}

irb(main):005:0> sh.delete("sex")
=> "male"
irb(main):006:0> sh
=> {"name"=>"shan", "age"=>"33"}  => ok here
irb(main):007:0> su
=> {"name"=>"shan", "age"=>"33"}   => ??

irb(main):010:0> sh["city"] = "Bangalore"  => New example
=> "Bangalore"
irb(main):011:0> sh 
=> {"name"=>"shan", "age"=>"33", "city"=>"Bangalore"} =>  ok
irb(main):012:0> su 
=> {"name"=>"shan", "age"=>"33", "city"=>"Bangalore"}  => ??  

Solution

  • This is not a problem if passing by reference or value. It is just two variables pointing to the same object and that object can be modified (adding keys, removing keys, altering values).

    You can check this by using object_id

    3.0.0 :002 > sh.object_id
     => 260
    3.0.0 :004 > su.object_id
     => 260
    

    It is this line in your program

    su = sh
    

    which tells Ruby that you want the Hash that you call sh also to be known as su. Kind of calling me by name or nickname:-)

    Not that the output of object_id might differ between ruby versions and runs of your program.

    There are various ways to have two separate hashes (or other objects:

    Create two separate objects with the initializer:

    sh = {"name"=>"shan", "age"=>"33" , "sex"=>"male"}
    su = {"name"=>"shan", "age"=>"33" , "sex"=>"male"}
    

    Create a clone:

    sh = {"name"=>"shan", "age"=>"33" , "sex"=>"male"}
    su = sh.clone
    

    #clone is defined for the standard ruby classes (e.g. Strings and Numbers). If you store something other in your Hash, then you might need to implement it on your own to get a correct copy.

    3.0.0 :005 > su = sh.clone
    3.0.0 :006 > sh.object_id
     => 260
    3.0.0 :007 > su.object_id
     => 280
    

    Now it gets interesting though. You only cloned the Hash, not the objects that you have stored inside it:

    sh = {a: "Hi"}
    su = sh.clone
    3.0.0 :010 > sh[:a].object_id
     => 300
    3.0.0 :011 > su[:a].object_id
     => 300
    

    that means if you modify an object inside your hash, it will also change the object that is referenced by the other hash:

    Depending on what you want, you can assign new objects:

    sh = {a: "Hi"}
    su = sh.clone
    
    sh[:a] = "HI
    sh[:a]
    # => "HI"
    su[:a] 
    # => "Hi"
    

    or modify them inplace

    sh = {a: "Hi"}
    su = sh.clone
    sh[:a].upcase! # this changes the string inplace
    
    sh[:a]
    # => "HI"
    su[:a]
    # => "HI"
    

    clone makes a shallow copy, if you want to have a complete copy, where clone is called on every object you can do the Marshal load/dump trick described by Denny Mueller or look into ActiveSupports #deep_dup https://guides.rubyonrails.org/active_support_core_extensions.html#deep-dup

    Or you can write an implementation of this on your own and add it to your hash.

    (there are also some gems out there)