Ruby 3.2 - Data / Struct

Ruby 3.2 アドベントカレンダーの10日目の記事です。

qiita.com


Data

Feature #16122: Data: simple immutable value object - Ruby master - Ruby Issue Tracking System

Ruby 3.2 で Data クラスが新設された。Struct とほぼ同じなんだけどオブジェクト作った後に値を変更することができない。

Data をそのまま使うんじゃなくて、Data.defineData の子クラスを作って使う。

Hoge = Data.define(:name, :value)
hoge = Hoge.new('abc', 123)        #=> #<data Hoge name="abc", value=123>
Hoge.new(name: 'abc', value: 123)  # これでも同じ
Hoge['abc', 123]                   # これでも同じ

hoge.name   #=> "abc"
hoge.value  #=> 123
hoge.name = 'xyz'  #=> undefined method `name=' for #<data Hoge name="abc", value=123> (NoMethodError)

StructStruct.new で子クラスを作ったんだけど、字面的に new はイマイチじゃない?ってことで Data.define になったらしい。

なお、Data.new は NoMethodError になる。

Struct

Feature #16806: Struct#initialize accepts keyword arguments too by default - Ruby master - Ruby Issue Tracking System

Struct.new で構造体クラスを作成できる。

Hoge = Struct.new(:abc, :xyz)
hoge = Hoge.new(123, 456)
hoge.abc  #=> 123
hoge.xyz  #=> 456

このようにして作成されたクラスは Ruby 3.1 ではキーワード引数では初期化できない。Hash として第1引数に設定される。warning も出る。

hoge = Hoge.new(abc: 123, xyz: 456)
#=> warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v).
hoge.abc  #=> {:abc=>123, :xyz=>456}
hoge.xyz  #=> nil

Struct.newkeyword_init を指定するとキーワード引数で初期化できるクラスを作成する。ただし位置引数では指定できない。

Hoge = Struct.new(:abc, :xyz, keyword_init: true)
hoge = Hoge.new(abc: 123, xyz: 456)
hoge.abc  #=> 123
hoge.xyz  #=> 456
hoge = Hoge.new(123, 456)
#=> wrong number of arguments (given 2, expected 0) (ArgumentError)

Ruby 3.2 から keyword_init を指定しなくてもよくなった。位置引数でもキーワード引数でもどちらでも初期化できる。

Hoge = Struct.new(:abc, :xyz)
Hoge.new(123, 456)
#=> #<struct Hoge abc=123, xyz=456>
Hoge.new(abc: 123, xyz: 456)
#=> #<struct Hoge abc=123, xyz=456>

たぶん新しく作られた Data に合わせたのかな。便利。