0

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"}  => ??  
Shan Sunny
  • 92
  • 10
  • I am unable to reproduce this in my irb REPL. Could you share your ruby type and version? This looks like `sh` and `su` are passing by reference like you would see in a language like C - but Ruby passes by value so unsure how you are seeing this. – kykyi Aug 11 '21 at 10:24
  • This is the behaviour I'd expect to see in ruby - but only for complex objects. For its own reasons, Ruby clones simple variables (like integers) when you assign a new name, but doesn't do this for hashes or arrays. – JohnP Aug 11 '21 at 10:52
  • You are not passing the hash as an argument anywhere in your code, so how can this even have anything to do with argument passing? However, note that Ruby is *always* pass-by-value. No exceptions. But again, since you are not passing the hash as an argument anywhere, argument passing is *completely irrelevant*. – Jörg W Mittag Aug 11 '21 at 15:55
  • 1
    @bashford7: "This looks like sh and su are passing by reference like you would see in a language like C" – C is pass-by-value, just like Ruby. And as I mentioned in my other comment: neither `sh` nor `du` are ever passed as an argument anywhere in the code, so argument passing *cannot possibly be relevant to the problem*. – Jörg W Mittag Aug 11 '21 at 15:56
  • @JohnP Ruby does not "clone" Integers. Integers are immediate, immutable objects: "...core numeric classes such as Integer are implemented as immediates, which means that each Integer is a single immutable object which is always passed by value.". The documents for [`Numeric#clone`](https://ruby-doc.org/core-3.0.0/Numeric.html#method-i-clone) state: "returns the receiver" so even "cloning" a `Numeric` does nothing. – engineersmnky Aug 11 '21 at 16:37
  • Thanks for the explanation, @engineersmnky. I'd wondered why some objects showed this issue while others didn't. (Interestingly, String behaves like Numeric, even though strings are mutable. I'm sure there's a good reason! And all that really matters, when writing code, is to know how the different object types behave. – JohnP Aug 11 '21 at 21:06

3 Answers3

2

The clone method is Ruby's standard, built-in way to do a shallow-copy:

su = sh.clone

Remark: the above solution is only for non nested hashes. In case of nested hashed you need to marshal

su = Marshal.load(Marshal.dump(sh))
Denny Mueller
  • 3,505
  • 5
  • 36
  • 67
  • 1
    To clarify this answer (which is basically correct): when you assign a variable name to an object, Ruby doesn't actually copy the object. So what you have is a single object (your hash) with two different names. Whichever name you use, you modify the same hash. The `clone` method does what the name implies: it takes a copy of the object. So, if you use this during assignment (as in the example above), your new variable name refers to that copy and *not* the original object. So, now, you can change one version without affecting the original. HTH! – JohnP Aug 11 '21 at 10:47
  • @JohnP Yes thanks, was about to make an edit to give more explanation to the memory reference. But your comment is should be sufficient. You seem to follow me and make comments on my answers aren't you? :) – Denny Mueller Aug 11 '21 at 10:51
  • Not intentionally... – JohnP Aug 11 '21 at 10:53
2

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)

Pascal
  • 8,464
  • 1
  • 20
  • 31
-1

.delete remove data from memory

you can use this: su = Hash[sh]