Reputation: 2203
Is there a way to pass objects by value and not by reference in Ruby? For Example,
class Person
attr_accessor :name
end
def get_name(obj)
obj.name = "Bob"
puts obj.name
end
jack = Person.new
jack.name = "Jack"
puts jack.name
get_name(jack)
puts jack.name
the output should be
Jack
Bob
Jack
instead of
Jack
Bob
Bob
Any help would be appreciated.
Upvotes: 3
Views: 1219
Reputation: 1367
No. Ruby passes by reference, not value.
If you need to simulate passing by value, you can use Ruby's Object#clone
method. In this case, you'd do something like this:
def get_name(obj)
new_object = obj.clone
new_object.name = "Bob"
puts new_object.name
end
This makes a shallow copy of an object. In other words, an object's instance variables are copied, but the objects the variables reference aren't copied. If you need to do a deep copy, you can read this Stack Overflow post. Ruby doesn't have a one-method way to perform deep copies, but that post describes how to use marshalling and unmarshalling to make a deep copy.
clone
and dup
work very similarly, but there are some differences. According to the docs:
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference. Copies the frozen and tainted state of obj. See also the discussion under Object#dup.
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference. dup copies the tainted state of obj. See also the discussion under Object#clone. In general, clone and dup may have different semantics in descendant classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendant object to create the new instance.
This method may have class-specific behavior. If so, that behavior will be documented under the #initialize_copy method of the class.
You can take a look at the dup and clone docs.
While my answer probably gives what the OP was looking for, it is not strictly correct, with respect to the semantics of passing by reference or value. See the other answers and comments on this page for some more discussion. You can also look at the discussion in the comments here and this post for more information.
Upvotes: 5
Reputation: 78523
Tossing in a formal answer the original question, on top of @Michael's excellent answer:
Is there a way to pass objects by value and not by reference in Ruby?
No. Absolutely not. Not possible. Really no, just forget it.
Or more precisely, if we nitpick on vocabulary, Ruby passes references to objects by value, and the passed objects are mutable as a result.
In Ruby, you must either:
Rubyists tend to pick option 2, because the internals are built that way (attr_reader, etc. return object state), because of performance reasons, and because creating deep copies of everything you return or manipulate is by no means trivial in practice.
Answering Jörg's comment:
def foo(bar) bar << ' is now mutated' end;
baz = 'Baz';
foo(baz);
puts baz # Baz is now mutated
Adding a further note/example on freezing, since this can give unexpected results as well:
foo = {foo: 'bar'}.freeze # {:foo=>"bar"}
foo[:foo] += 'baz' # RuntimeError: can't modify frozen Hash
foo[:foo] << 'baz' # {:foo=>"barbaz"}
Upvotes: 2
Reputation: 369536
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'
Upvotes: 2