[Ruby] Get in the habit of using the dup method when making a copy of a string variable

Preface

I'm programming in Ruby and I'm addicted to it, or I'm addicted to it, so don't forget.

environment

For the time being, version information as well. But it's basic, so it shouldn't be version-related.

When making a copy of the value of a variable ...

When you make a copy of the value of a variable, do you just have to make an assignment?

@bob = "I love you"
@carol = @bob         # <=here!

puts "@bob = #{@bob}, @carol = #{@carol} "
    # => @bob = I love you, @carol = I love you

When you make a copy of the value of a variable, you usually have the purpose of "keeping the value of the original variable and using the copied variable for various operations". Even if the value of the copied variable changes and it breaks, the original variable is saved, so it's okay.

# ---Continued above---
@carol = @carol + ", too"
puts "@bob = #{@bob}, @carol = #{@carol} "
    # => @bob = I love you, @carol = I love you, too

As shown above, even if you add to the character string to the variable @carol of the assignment destination, the value of the variable @ bob of the assignment source does not change ~~ Naturally ~~.

However, if you perform a "destructive operation" such as replacing the character string inside the variable of the assignment destination, the value of the assignment source will also be affected.

@bob = "I love you"
@carol = @bob

@carol.sub!("love", "hate")

puts "@bob = #{@bob}, @carol = #{@carol} "
    # => @bob = I hate you, @carol = I hate you

I should have changed the value of @ carol, but the value of @ bob has also changed.

The reason for this is that in Ruby everything is an object, and the assignment operator is "passing by reference" which copies the ~~ reference destination ~~ copying the object reference. [^ 1] If you say @carol = @bob, then @carol and @bob refer to the same string object. So, if you do a "destructive operation" on @ carol, both will change because the object referenced by @ bob is the same.

By the way, even with the same operation, if you change it using the sub method instead ofsub!, The behavior will change again.

# ---Continued above---
@bob = @bob.sub("hate", "love")                                                

puts "@bob = #{@bob}, @carol = #{@carol} "                                     
    # => @bob = I love you, @carol = I hate you

I changed the string @ bob to love in the same way, but @ carol is still hate. .. .. I hated you bob. .. ..

But why?

In the case of sub!, the contents of that object are changed directly, but in the case of sub, the replaced character string is" generated as a new object "and assigned to @ bob again. Because there is. The referenced object has changed from the one referenced with @carol to a new one.

For the same reason, in the first example, adding ", too" to the @ carol string did not change the value of @ bob. By adding another string to the @ carol string, a" new object "of the combined string was created and reassigned to @ carol, so it is referred to as @bob. This is because the object has changed.

In this way, if you are relieved to think that you simply copied the value by assignment, the value of the assignment source may change depending on the subsequent processing, but it may be okay depending on the processing order. Will happen.

Then what do you do?

As I wrote earlier, the situation where you want to copy a variable is because you want to save the value of the copy source, so I do not assume that if you change the value of the copy destination, the value of the copy source will also change. It's a bad thing. You don't need to copy it if you can change it.

Then what should I do?

That's why the dup method comes out. The dup method is a method that duplicates an object, that is, creates another object with the same contents.

@bob = "I love you"
@carol = @bob.dup      # <==Here in the dup method@Create a new object that copies the value of bob and assign it

@carol.sub!("love", "hate")

puts "@bob = #{@bob}, @carol = #{@carol} "
    # => @bob = I love you, @carol = I hate you

It looks the same, but this time only @carol has changed to hate. After all bob is hated. .. .. This is because the object created by the dup method is different from the object that @ bob has.

You can use the ʻobject_id` method to see if the objects match.

@bob = "I love you"
@carol = @bob
puts @bob.object_id    # => 46779560
puts @carol.object_id  # => 46779560

@anna = @bob.dup
@anna.object_id        # => 46931780

Certainly, if you assign it as it is, it will have the same object ID, and if you create it with the dup method, it will have a different object ID.

By doing this, even if the value is the same, it is a completely different object, so no matter how you play with it, the original value will not change without permission.

In fact, I think that there are many cases where there is no problem with just "assignment" because the object changes in the subsequent operation. However, there is a high risk of inducing a bug if you inadvertently affect it. So

When copying variables, do not assign, use the dup method or something to duplicate properly

I will do my best so that I can do things unconsciously (I tell myself ...)

In addition ...

In addition to the dup method, there is a clone method for replicating objects. You may also use marshal_dump, marshal_load.

For duplication of string objects, dup is sufficient. In addition to dup, clone also duplicates a copy of the singular method and the frozen state of the object. When duplicating arrays, hashes, and classes, dup and clone do not duplicate elements and internal variables, which is inadequate. In that case, you need to use marshal_dump, marshal_load, and override the method if necessary.

Please check the detailed story around here. (I have to study too ...)

[^ 1]: The word "pass by reference" has been removed.

Recommended Posts