ホーム DoRuby Rubyのオブジェクトコピーで気をつけること
Rubyのオブジェクトコピーで気をつけること
 

Rubyのオブジェクトコピーで気をつけること

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Rubyでオブジェクトを複製する際、知らないとハマってしまいそうなポイントをメモ。

その1:=で代入されるのはオブジェクトの参照

=で代入後、破壊的メソッドを使う場合は注意が必要です。

list1 = ['a', 'b', 'c']``
list2 = list1

list1[0] = 'aa'

list1
=> ["aa", "b", "c"]
list2
=> ["aa", "b", "c"]

list1の変更がlist2にも反映されてしまいました。
初学時に混乱しがちなパターンですが、

list2 = list1

は、list2にlist1の参照を代入する、の意なので、
どちらも同じオブジェクトを参照することになります。

object_idを確認すると、同じオブジェクトを参照していることが分かります。

list1.object_id
=> 70343473882920
list2.object_id
=> 70343473882920

破壊的にオブジェクトの内容が変更された場合、
そのオブジェクト見ている他の変数にも変更内容が反映されます。
 
 

その2:dup、cloneは浅いコピー

これを回避するためにはdup、cloneを使います。
が、いくつか注意点があります。

まずはdup、cloneとも別のオブジェクトとして複製されることを確認。

list3 = [{key:'xx'},{key:'yy'},{key:'zz'}]
list4 = list3.dup
list5 = list3.clone

list3.object_id
=> 70343473688700
list4.object_id
=> 70343473670940
list5.object_id
=> 70343473655820

参照しているオブジェクトが違うので、
list3の変更はlist4, list5に影響しません。

list3.reverse!
=> [{:key=>"zz"}, {:key=>"yy"}, {:key=>"xx"}]
list4
=> [{:key=>"xx"}, {:key=>"yy"}, {:key=>"zz"}]
list5
=> [{:key=>"xx"}, {:key=>"yy"}, {:key=>"zz"}]

しかしこうするとどうでしょう。

list3[0][:key]='ZZ'

list3
=> [{:key=>"ZZ"}, {:key=>"yy"}, {:key=>"xx"}]
list4
=> [{:key=>"xx"}, {:key=>"yy"}, {:key=>"ZZ"}]
list5
=> [{:key=>"xx"}, {:key=>"yy"}, {:key=>"ZZ"}]

それぞれ別オブジェクトなのにlist3の変更がlist4, list5にも反映されてしまいました。
 

dup, cloneは、複製するオブジェクト自体は別物になりますが、
それに含まれる参照先はそのままコピーされるため、
その1と同じ事象が発生してしまいます。(参照先のobject_idが同じ)

list3[0].object_id
=> 70343473688720
list4[2].object_id
=> 70343473688720
list5[2].object_id
=> 70343473688720

これは「浅いコピー」といいます。

dupとcloneはほぼ同じですが、以下の点が異なります。

複製するもの


dup
:汚染状態、信頼状態


clone
:汚染状態、信頼状態、凍結状態、特異メソッド


複製できないオブジェクト


true、false、nil、シンボル、数値

■リファレンス
https://docs.ruby-lang.org/ja/latest/class/Object.html#I_CLONE
 
 

その3:Marshalで深いコピー

Marshalを使うとこの問題が解決します。
Marshalはオブジェクトをシリアライズ/デシリアライズするモジュールで、
いったん文字列(ファイル)として保存したものを読み戻すため、
まったく別のオブジェクトとして複製することができます。

Marshal.load(Marshal.dump(obj))


list6 = Marshal.load(Marshal.dump(list3))
list3[1][:key]='YY'

list3
=> [{:key=>"ZZ"}, {:key=>"YY"}, {:key=>"xx"}]
list6
=> [{:key=>"ZZ"}, {:key=>"yy"}, {:key=>"xx"}]

■リファレンス
https://docs.ruby-lang.org/ja/latest/class/Marshal.html

破壊的メソッドを使う際には、
うっかり複製元/先に変更が影響してしまわないよう
注意が必要です。

記事を共有

最近人気な記事