Hack Your Design!

Ruby2.3 で導入された frozen_string_literal オプションで Immutable String を実現する

Immutable String in Ruby3

Ruby3 では文字列がデフォルトで immutable になるという大きな変更が予定されている。

Ruby 3.0 では文字列リテラルをデフォルトで immutable (破壊的変更不可) にする、という方針が『決定』しました

via. [Ruby] Ruby 3.0 の特大の非互換について - まめめも

この変更の背景としては上リンクに書いてある通り、Rubyの最適化のために文字列のいたるところに.freezeを付けてプルリクエストを投げる輩が大挙してきたことだ。

Immutable String in Ruby2.3+

実は Ruby2.3 で既にこの Immutable String を有効にする機能が入っている。やり方はRubyファイルの行頭に次のようにマジックコメントを書けばよい。

# frozen_string_literal: true

frozen_string_literal の機能を試す

実際に試してみよう。frozen_string_literalの設定が入っているRubyコードと入っていないRubyコードの2つを用意して実行してみる。

frozen_string_literal入りのコード

string_with_frozen_option.rb

# frozen_string_literal: true
5.times { puts "a".object_id }

結果

全て同じ object_id が返ってくる。

$ ruby string_with_frozen_option.rb
70212460463280
70212460463280
70212460463280
70212460463280
70212460463280

frozen_string_literal無しのコード

string_without_frozen_option.rb

5.times { puts "a".object_id }

結果

全て違う object_id が返ってくる。

$ ruby string_without_frozen_option.rb
70277165754460
70277165754200
70277165754080
70277165754000
70277165753940

frozen_string_literal はファイル毎に設定される

たとえばfrozen_string_literalの設定が入ったものと入っていないファイルが実行された場合はどうなるだろうか。Railsで試してみる。

class ApplicationController < ActionController::Base
  before_action :not_frozen
  def not_frozen
    5.times { logger.debug("a".object_id) }
  end
end
# frozen_string_literal: true
class WelcomeController < ApplicationController
  def index
    5.times { logger.debug("a".object_id) }
  end
end

これでWelcomeController#indexが実行された場合、ログは下記のようになる。

70346238891860
70346238891080
70346238890280
70346238889340
70346238888420
...
70346229343820
70346229343820
70346229343820
70346229343820
70346229343820

つまりWelcomeControllerで定義された文字列だけがfreezeされていることがわかる。

mutableなStringを定義するにはどうしたらよい?

一度 frozen_string_literal: true のコードを入れると全ての文字列が.freezeされるので、下記のようなコードはRuntimeErrorとなる。

# frozen_string_literal: true
str = "a"
str << "bc"
puts str
# => test.rb:3:in `<main>': can't modify frozen String (RuntimeError)

この場合の対処法としてはfreezeを解除したい文字列に対して、.dupを付けてやれば解決する。

# frozen_string_literal: true
str = "a".dup
str << "bc"
puts str
# => abc

まとめ

Ruby3 の Immutable String に先駆けて、Ruby2.3 以上が前提の実行環境では積極的にfrozen_string_literal: trueの設定をしていくべき。

参考

  • このエントリーをはてなブックマークに追加
comments powered by Disqus