結論
まずざっくり結論
- Ruby3.2で組み込みライブラリとしてDataクラスが登場した
- DataクラスはイミュータブルのStructクラスのようなイメージ
- 既存のコード等で Ruby3.0〜Ruby3.1系のコードの中で Dataクラスを作っているコードだと Ruby3.2に上げるとクラス名が衝突するかもしれなくて危ないよ
はじめ
単純かつ不変な値オブジェクトを表現するための新たなコアクラス Data が追加されました。 Data は Struct によく似ており、部分的に実装を共有しています。しかし、より限定的かつ 少ないAPIとなっています。 [Feature #16122]
引用: https://www.ruby-lang.org/ja/news/2022/12/25/ruby-3-2-0-released/
今回はRuby3.2で組み込みライブラリとして登場したDataクラス を紹介します
動かしてみる 1
irb(main):001:0> User = Data.define(:name, :age, :sex)
=> User
irb(main):002:0> user1 = User.new('taro', 10, 'man')
=> #<data User name="taro", age=10, sex="man">
irb(main):003:0> user2 = User.new('hanako', 12, 'woman')
=> #<data User name="hanako", age=12, sex="woman">
irb(main):004:0> user1.name
=> "taro"
概要
Dataクラスとは先述したように define
するとDataクラスのサブクラスを作成できるものです。
以下を実行すると確かにスーパークラスがDataクラスになっていることがわかります
irb(main):005:0> User.superclass
=> Data
また define
する際に引数を渡すとそれがアクセスメソッドとしてサブクラスに定義されます
ただこれってStructクラスと同じなのでは? 思われる方もいらっしゃるかもしれません。
大丈夫です。確かにStructクラスと似ておりますが、違うところももちろんあります!
その中で大きな違い、それは
- Struct classは ミュータブル
- Data classは イミュータブル
なのです!!
「...それじゃあイミュータブルとは???」
聞き馴染みのない方もいらっしゃるかもしれないのでStructとDataでサブクラス、インスタンスを作成しながら解説しようと思います
StructとDataクラスを使ってミュータブル、イミュータブルを知ろう
それぞれ日本語訳すると以下のようになります
- ミュータブル(mutable): 可変
- イミュータブル(Immutable): 不変
勘の良い方だとなんとなく理解できたかと思います
実際にそれぞれStructとDataクラスでサブクラスを作ってそれぞれインスタンスを作ってみます
irb(main):001:0> User1 = Struct.new(:name, :age, :sex)
=> User1
irb(main):002:0> User2 = Data.define(:name, :age, :sex)
=> User2
irb(main):003:0> user1 = User1.new('taro', 10, 'man')
=> #<struct User1 name="taro", age=10, sex="man">
irb(main):004:0> user2 = User2.new('hanako', 10, 'woman')
=> #<data User2 name="hanako", age=10, sex="woman">
この時、作ったインスタンスの値を参照することはできます
irb(main):005:0> user1.age
=> 10
irb(main):006:0> user2.age
=> 10
それでは値を更新してみて値を再度確認してみましょう
irb(main):007:0> user1.age = 11
=> 11
irb(main):008:0> user2.age = 11
(irb):8:in `<main>': undefined method `age=' for #<data User2 name="hanako", age=10, sex="woman"> (NoMethodError)
from /Users/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
from /Users/.rbenv/versions/3.2.0/bin/irb:25:in `load'
from /Users/.rbenv/versions/3.2.0/bin/irb:25:in `<main>'
irb(main):009:0> user1
=> #<struct User1 name="taro", age=11, sex="man">
irb(main):010:0> user2
=> #<data User2 name="hanako", age=10, sex="woman">
このようにDataクラスで作成されたサブクラスのインスタンスの値を再定義することができない=不変。これがミュータブルというものです
他にも細かい違いはありますが、それぞれのドキュメントを読んでいただけると理解が深まると思います!
Struct: https://docs.ruby-lang.org/ja/latest/class/Struct.html
Data: https://docs.ruby-lang.org/ja/latest/class/Data.html
Ruby 3.0 以降から Ruby 3.2にあげる際に Data classがあると危ない
ここまででRuby3.2 で登場したDataクラスの概要の説明は終わりましてここでタイトルの内容です。
Dataクラスに限った話ではないですが、新規クラスが作られたことはつまりRubyのバージョンアップ等を実施するときに過去バージョンでDataクラスを自分で作っていた場合クラスが衝突してしまう恐れがあるということです!
実際に動かしてみましょう
動かしてみる 2
# ruby 3.1の場合
irb(main):001:1* class Data
irb(main):002:2* def initialize(x, y)
irb(main):003:2* @x = x
irb(main):004:2* @y = y
irb(main):005:1* end
irb(main):006:0> end
=> :initialize
irb(main):007:0> Data.new(1,2)
=> #<Data:0x0000000110f74a48 @x=1, @y=2>
# ruby 3.2の場合
irb(main):001:1* class Data
irb(main):002:2* def initialize(x, y)
irb(main):003:2* @x = x
irb(main):004:2* @y = y
irb(main):005:1* end
irb(main):006:0> end
=> :initialize
irb(main):007:0> Data.new(1,2)
(irb):7:in `<main>': undefined method `new' for Data:Class (NoMethodError)
from /Users/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
from /Users/.rbenv/versions/3.2.0/bin/irb:25:in `load'
from /Users/.rbenv/versions/3.2.0/bin/irb:25:in `<main>'
Ruby 3.2ではうまく機能しない
見て頂いた通りRuby3.1では正常にDataクラスをinitializeできたのに対し、Ruby3.2ではすでにDataクラスが存在し define
で定義する作りになっているためクラス名が衝突し、自分で定義した通りに動きませんでした。
Data
という名前は割と汎用的だと思うのでもしかしたらDataクラスを自分で定義されている方もいらっしゃるかもしれません。その場合Ruby3.2にバージョンをあげる際は自分の定義しているDataクラスを別の名前に変更することを勧めます。
※ ちなみにRuby 2.7まではDataクラスは存在しており3系になって一度削除されました。
ですのでここ2,3年の間で3.0〜3.1系でのRuby開発のものが主な対象だと思います