Issue with removing/deleting element from Ruby Array
Today I've Learned postImagine this situation:
a = [1, 2, 3 ]
b = a
b.delete(2)
b
#=> [1, 3]
a
#=> [1, 3]
Similar dangerous situation happens when you add elements to Array:
c = [1, 2, 3 ]
d = c
d << 4
d
#=> [1, 2, 3, 4]
c
#=> [1, 2, 3, 4
This is not really a issue. It’s how Ruby works.
Explanation
In Ruby lang variables are assigned as a reference - they point to same object (source)
a = []
a ---------> object
Therefore a
is just reference to object in computer memory
When you do
a = b
You really set in b
the same reference as a
is pointing to
a ------\
>----> object
b ------/
It’s not just Array
This doesn’t really have anything to do with Array, specifically. Ruby assigns by reference, so any method call that changes its receiver in-place has the potential to manifest this behavior.
Hash example:
x = {id: 1, name: 'allisio', skill: 'pro' }
y = x
y.delete(:id)
y
# => {:name=>"allisio", :skill=>"pro"}
x
# => {:name=>"allisio", :skill=>"pro"}
y[:lang] = 'ruby'
y
# => {:name=>"allisio", :skill=>"pro", :lang=>"ruby"}
y
# => {:name=>"allisio", :skill=>"pro", :lang=>"ruby"}
String example:
a = 'abcd'
b = a
b.gsub!('ab', 'xx')
b
# => 'xxcd'
a
# => 'xxcd'
Custom object example:
class Foo
attr_accessor :value
end
foo = Foo.new
foo.value = 1
bar = foo
bar.value =2
bar.value
# => 2
foo.value
# => 2
Solution
Here are few options how to tell Ruby to reference a new object
Clone the object
a = [1,2,3]
b = a.dup
b.delete(2)
b
# => [1, 3]
a
# => [1, 2, 3]
Do functional calculation
There is famous statement in Functional programming world: “State is root of all evil”
You can performing functional operation with the Array a
that results in returning new array:
a = [1,2,3]
b = a - [2]
b
# => [1, 3]
a
# => [1, 2, 3]
Same for other objects
a = 'abcd'
b = a.gsub('ab', 'xx')
a
# => 'abcd'
b
# => 'xxcd'
Freeze your Object
In order to prevent your Junior developers to do this mistake you may want to freeze the Array
a = [1,2,3].freeze
b = a
b.delete(2)
# FrozenError (can't modify frozen Array)
b
# => [1,2,3]
a
# => [1,2,3]
Special Thanks
In first version of the article I’ve described the problem from wrong perspective
Thank you allisio for the help on describing the problem correctly
Related Articles
Here are some article that goes deeper into this topic:
- https://launchschool.com/blog/object-passing-in-ruby
- https://launchschool.com/blog/references-and-mutability-in-ruby
Similar topics:
Discussion
Entire blog website and all the articles can be forked from this Github Repo