Edited at

crystalのシャローコピー、ディープコピーについて

More than 3 years have passed since last update.

ブログからの転記です。

slack上でのcrystal-jaコミュニティで、いろんな議論(大体私からは不明点を聞くだけ)してるのですが、 ちょっとcyrstalで気になってたのが、シャローコピー、ディープコピーはどうするのか、というもの。

例えば、以下のコードはrubyだと


class ClassA
attr_accessor :a
end

clsa = ClassA.new
clsa.a = 1
clsb = clsa.clone
clsb.a = 2
p clsb.a #=> 1
p clsa.a #=> 2

となります。 では、crystalではどうなるかというと


(同上)
p clsb.a #=> 2
p clsa.a #=> 2

となってしまいます。(attr_accessorはpropertyで読み替え)

どうもcyrstalでのやり方では、

https://github.com/manastech/crystal/blob/b3b1223108806f31fdb29cca6110a5d11d82f504/src/object.cr#L173-L179

https://github.com/manastech/crystal/blob/0f4e5866db1d559fdb3c833e8de8a1f96ab7c957/src/array.cr#L604-L622

を見る限りだと、「子クラスで override して使う前提」みたいです。 (@pine613さんの見立てでは) なので、ディープコピーの各クラスの実装例だと、以下のような感じになります。(同@pine613さんのコード)

class ClassA

property :a

def self.new
i = self.new
yield i
i
end

def clone
ClassA.new { |i|
i.a = self.a
}
end
end

因みにですが、crystalではStructクラスも用意されています。

classクラスとの違いは

http://ja.crystal-lang.org/docs/syntax_and_semantics/structs.html

を見ると


  • 構造体に対して new を実行するとヒープではなくスタック領域が割り当てられる

  • クラスが参照渡しであるのに対して、構造体は値渡しである

  • 構造体は暗黙的に Struct を継承し、Struct は Value を継承している。一方クラスは Reference を継承する

とあります。オブジェクト間で値をやり取りするようなことがあるなら、こっちを使うべき、という設計みたいですね。

@makenowjustさん曰く

「それと、 record マクロを使って定義した構造体には clone メソッドが定義されてます」

とのことで、実際に調べてみると以下のようになっています。

https://github.com/manastech/crystal/blob/0f4e5866db1d559fdb3c833e8de8a1f96ab7c957/src/macros.cr#L29-L42

上記情報に従って書き直すと

struct ClassA

property :a
end

record ClassA

classA = ClassA.new
classA.a = 1

classB = classA.clone
classB.a = 2

p classA.a #=> 1
p classB.a #=> 2

となり期待する動作になりました。