`Style/MutableConstant` cop の有効化による Ractor 安全性への前進
Rails コードベース全体で、リテラル定数への .freeze 付与を強制する Style/MutableConstant RuboCop cop が有効化されました。これは Rails の Ractor 安全化に向けた取り組みの一環であり、フレームワーク起動後に変更されない静的定数を不変オブジェクトとして明示します。
背景
この変更は、Rails を Ractor 対応にするための継続的な取り組みの一部として行われました。Ractor 間で共有されるクラスやモジュールは、そのすべての定数も共有可能(shareable)である必要があります。定義時に .freeze を付与することで、定数をイミュータブルオブジェクトとして宣言でき、Ractor 安全性の要件を満たせます。
これまで Rails コードベースには、配列・ハッシュ・文字列リテラルで定義された定数に .freeze が付いていないケースが多数存在していました。これらはフレームワークのライフタイムを通じて事実上変更されない静的な値ですが、Rubyのオブジェクトとしては可変(mutable)な状態でした。
技術的な変更
.rubocop.yml に Style/MutableConstant cop が追加され、EnforcedStyle: literals で有効化されました。この設定により、リテラル値(Array・Hash・String 等)として定義された定数に .freeze が付いていない場合に警告が出るようになります。
変更前:
Style/FrozenStringLiteralComment:
...
Style/MapToHash:
Enabled: true
変更後:
Style/FrozenStringLiteralComment:
...
Style/MutableConstant:
Enabled: true
EnforcedStyle: literals
Exclude:
- '**/*.md'
Style/MapToHash:
Enabled: true
コードベース全体では、Array・Hash・String リテラルで定義された多数の定数に .freeze が追加されました。代表的な変更例を示します。
配列リテラルの例(action_dispatch/http/request.rb):
# 変更前
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
# 変更後
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT).freeze
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK).freeze
ハッシュリテラルの例(action_cable/lib/action_cable.rb):
# 変更前
protocols: ["actioncable-v1-json", "actioncable-unsupported"].freeze
}
# 変更後
protocols: ["actioncable-v1-json", "actioncable-unsupported"].freeze
}.freeze
文字列リテラルの例(action_dispatch/http/mime_type.rb):
# 変更前
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")"
MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
# 変更後
MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}".freeze
MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")".freeze
MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?".freeze
また、action_dispatch/http/request.rb には require "active_support/core_ext/enumerable" の追加も含まれています。
一方、キャッシュや外部からの拡張を意図した定数については、.freeze を付与せず # rubocop:disable Style/MutableConstant コメントで明示的に除外しています。該当する定数は以下のとおりです:
-
Mime::EXTENSION_LOOKUP/Mime::LOOKUP(MIMEタイプの登録テーブル) -
ActionDispatch::Journey::Path::Pattern::REGEXP_CACHE(正規表現キャッシュ) -
ActionDispatch::Journey::Visitors::Visitor::DISPATCH_CACHE/FunctionalVisitor::DISPATCH_CACHE(ディスパッチキャッシュ) -
ActionDispatch::Http::Request::HTTP_METHOD_LOOKUP(HTTPメソッドルックアップキャッシュ)
設計判断
EnforcedStyle: literals が採用された点が重要です。literals スタイルは、Array・Hash・String などのリテラル値で定義された定数のみを対象とします。メソッド呼び出しの結果や動的に構築される定数は対象外であるため、既存コードへの影響を最小限に抑えながら、静的な定数の不変性を保証できます。
変更不可能にすべきでない定数(キャッシュや外部拡張ポイント)については、修正を後回しにして rubocop:disable コメントで明示しています。これにより、「この定数はミュータブルであることが意図的である」という設計意図がコード上に残ります。一括適用と段階的修正を組み合わせたアプローチは、大規模コードベースでの静的解析導入の実践例としても参考になります。
また、actionpack/lib/action_controller/strong_parameters.rb の EMPTY_ARRAY と EMPTY_HASH が freeze されたことは特筆に値します。これらは名前のとおり空の配列・ハッシュであり、フリーズすることでオブジェクトの再利用時の安全性が高まります。
まとめ
このPRは、RuboCop の Style/MutableConstant cop を literals スタイルで有効化し、Rails 全体の静的定数に .freeze を付与することで、Ractor 安全化の基盤を整備しました。ミュータブルであることが意図的な定数を rubocop:disable で明示的に区別する方針は、コードの設計意図の可視化にも貢献しています。