Ruby is pass-by-value. Always. Here's a simple program which demonstrates that fact:
def foo(bar)
bar = 'reference'
end
baz = 'value'
foo(baz)
puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value
What you are seeing is simply shared mutable state: if an object is mutable, it can be mutated, and if that object is visible through multiple references, then that mutation will be visible through multiple references. Simple as that. It doesn't matter if you call me "Jörg" like my friends do or "son" like my mom does, if I cut my hair, both of you will see that.
If you don't want your objects to be mutated, you need to make them immutable. For example:
class Person
attr_reader :name
private
attr_writer :name
def initialize(name)
self.name = name
end
end
def get_name(obj)
obj.name = "Bob"
puts obj.name
end
jack = Person.new('Jack')
puts jack.name
# Jack
get_name(jack)
# NoMethodError: private method `name=' called for #<Person:0xdeadbeef081542 @name='Jack'>
puts jack.name
# Jack
Now, your Person
objects can no longer be changed by other objects. However, note that objects referenced by your Person
objects obviously can still be changed:
jack.name << ' the Ripper'
puts jack.name
# Jack the Ripper
jack.name.replace('Bob')
puts jack.name
# Bob
If you want to protect against that, you need to make sure that all the objects referenced by your Person
objects are also immutable. For example like this:
class Person
def initialize(name)
self.name = name.freeze
end
end
jack = Person.new('Jack)
jack.name << 'the Ripper'
# RuntimeError: can't modify frozen String
Now, your Person
objects are truly immutable. (At least as "truly" as can be in a language with such powerful reflection capabilities as Ruby.)
Unfortunately, we have now done to someone else the very same thing we are trying to protect ourselves against: we are mutating their objects! In Person#initialize
, we mutate the String
that is passed in by freeze
ing it. If the method that created the Person
wants to do something else with the String
, they are in for a nasty surprise:
name = 'Jack'
jack = Person.new(name)
name << ' the Ripper'
# RuntimeError: can't modify frozen String
We can fix that by making a copy of the String
first, before freeze
ing it:
class Person
def initialize(name)
self.name = name.dup.freeze
end
end
name = 'Jack'
jack = Person.new(name)
name << ' the Ripper'
# => 'Jack the Ripper'