`NumberConverter::DEFAULTS` のネストされたHashをdeep freezeに変更
NumberConverter::DEFAULTS 内のネストされたHashに .freeze を追加し、定数全体をdeep freezeにしました。これにより、定数がRactor間で安全に共有可能(shareable)になります。
背景
この変更は、RailsをRactorと組み合わせて使えるようにするための一連の取り組みの一部です。Ractorの安全性(Ractor-safety)を確保するには、Ractor間で共有されるクラス・モジュールが持つすべての定数が「shareable」である必要があります。
先行する #57323 では、Style/MutableConstant Copを literals スタイルで有効化し、リテラルで定義された定数に対して外側のオブジェクトへの .freeze 適用を徹底しました。しかし、このCopが保証するのは 外側のHash のfreezeのみであり、ネストされた内側のHashまでは凍結されません。NumberConverter::DEFAULTS はまさにこのケースに該当し、外側のHashはfreezeされていたものの、内側のHashは可変のままでした。
Ractorのshareability検査は再帰的に行われるため、ネストされたオブジェクトのいずれかが可変であれば、定数全体がunshareable扱いになります。
技術的な変更
activesupport/lib/active_support/number_helper/number_converter.rb 内の DEFAULTS 定数に含まれる、ネストされたすべてのHashリテラルに .freeze を追加しました。
変更が加えられたのは以下の5箇所のネストされたHashです:
-
numberキー配下のフォーマット設定Hash -
currencyキー配下のフォーマット設定Hash(およびcurrency自身のHash) -
percentageキー配下のフォーマット設定Hash(およびpercentage自身のHash) -
precisionキー配下のフォーマット設定Hash(およびprecision自身のHash) -
humanキー配下のフォーマット設定Hash
代表例として currency の変更前後を示します:
変更前:
currency: {
format: {
precision: 2,
significant: false,
strip_insignificant_zeros: false
}
},
変更後:
currency: {
format: {
precision: 2,
significant: false,
strip_insignificant_zeros: false
}.freeze
}.freeze,
すべての変更は .freeze の付加のみであり、定数の値や構造自体には一切手が加えられていません。
設計判断
RuboCopのCopが自動的に保証できる範囲(リテラルの外側のfreeze)と、それだけでは不十分なケース(ネストされたHashのdeep freeze)を明確に分け、後者を手動で補完するアプローチが採られました。
DEFAULTS は数値フォーマットのデフォルト設定を保持する純粋な定数であり、実行時に変更されることを意図していません。そのため、deep freezeにすることによる機能上の副作用はなく、変更は安全です。Style/MutableConstant Copでカバーしきれないネスト構造のfreezeを個別に対処するという判断は、Ractor対応を段階的に進める上での現実的な手法といえます。
まとめ
本PRは、NumberConverter::DEFAULTS のネストされたHashすべてに .freeze を追加することで、定数をRactor-safeな状態に引き上げた変更です。外側のfreezeだけではshareabilityを満たせないというRactorの仕様上の制約を踏まえ、deep freezeによって定数ツリー全体の不変性を保証しています。