How can I prevent a method from mutating its arguments?

Go To StackoverFlow.com

4

The method in question that I want to use is gem and sourced here (lines 17-42): https://github.com/rails/rails/blob/master/railties/lib/rails/generators/actions.rb

As you can see, name is assigned to the first arg on line 19, then message is assigned to name on line 23 and finally message is mutated on line 26 with <<. This unfortunately means that the string I am passing in as the first argument is mutated outside of the method.

I have a hash of arrays and am iterating over them as follows:

groups = { foo: %w(foo, bar), bar: %w(foobar) }

groups.each do |group, gems|
  gems.each do |name|
    gem(name, "42")
  end
end

Afterwards my hash looks like this due to the mutation inside of gem:

groups => { foo: ["foo (42)", "bar (42)"], bar: ["foobar (42)"] }

How can I prevent these strings (and the hash and its arrays) from being mutated but without breaking the method?

2012-04-03 20:39
by Rupert Madden-Abbott
Freezing the string will stop it being mutated, but only because it'll cause an exception to be raised - Andrew Grimm 2012-04-04 00:16


1

You may call it with name.dup:

gem(name.dup, "42")

Background: With gem(name) you pass the parameter to the method. Any modification inside the called method, will change also the the original variable.

With name.dup you make a copy of the object. This copy is modified inside the called method, but the original value is unchanged.


A warning: dup does not work always, it is depending on the data. dup doesn't make a deep copy. See this example:

arr = ['a', 'b']
arr.dup.map{|x| x << '1'}
p arr #["a1", "b1"]

Explanation: The array arr is copied, but not the content inside the array. Inside the map you modify the data of the copied array. But the elements of the original and the copied array are the same. So you also change the content of the original array.

2012-04-03 20:58
by knut
-1. There is no call-by-reference in Ruby. Ruby is always call-by-value. No ifs. No buts. No exceptions. For example: def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is call-by-#{baz}" - Jörg W Mittag 2012-04-03 22:46
@JörgWMittag def foo(bar) bar << ', but those values are typically references' end; baz = 'value'; foo(baz); puts "Ruby is call-by-#{baz}"dbenhur 2012-04-04 00:34
@JörgWMittag If Ruby is always call-by-value then why does << mutate the argument - Rupert Madden-Abbott 2012-04-04 09:12
@Rupert: Because that's how << is defined to behave? I don't understand what this has to do with being call-by-value or call-by-reference, though - Jörg W Mittag 2012-04-04 09:18
@JörgWMittag: Let me rephrase the question: If a parameter contains the value passed in to its method, how is it possible for a change in that value to affect whatever object was passed in to that parameter? My understanding of call-by-value is that parameters do not contain a reference to objects passed to them but, rather, the value of those objects are copied and then used internally. Is that incorrect - Rupert Madden-Abbott 2012-04-04 09:45
@JörgWMittag Yes, you are right, I was wrong. I corrected my answer. In this case the parameter quacked like a call by reference and I was mentally deranged. @rupert Take this example: def a(p); p = 5; end; x = 4;a(x);p x when you make a new assignment to the parameter, the original value is unchanged. But if you modify the object, you modify the original object. Maybe this wikipedia section for Call by sharing helps to understand it (and if I understand Jörg correct, the wikipedia article contains an error about ruby - knut 2012-04-04 19:18


5

You can't prevent the method from mutating its arguments (other than by filing a bug report, because it's something it shouldn't do).

What you can do is call the method with a clone of your string, like so:

gem(name.dup, "42")
2012-04-03 20:58
by Dominik Honnef
Thanks! Is it generally considered bad form to mutate arguments due to the very problem I have encountered - Rupert Madden-Abbott 2012-04-03 21:04
Yes exactly - the arguments should not be mutated in the methods, unless such behavior is expected and documented - Aleksander Pohl 2012-04-03 21:06