- 公開日
Ruby2.3で導入されたfrozen_string_literalマジックコメントでImmutable Stringを実現する
目次
Immutable String in Ruby3
Ruby3 では文字列がデフォルトで immutable になるという大きな変更が予定されている(追記あり)。
Ruby 3.0 では文字列リテラルをデフォルトで immutable (破壊的変更不可) にする、という方針が『決定』しました
via. [Ruby] Ruby 3.0 の特大の非互換について - まめめも
この変更の背景としては引用リンクに書いてある通り、Rubyの最適化のために文字列のいたるところに.freeze
を付けてプルリクエストを投げる輩が大挙してきたことだ。
追記(2019-08-07)
「Ruby3 では文字列がデフォルトで immutable になる」と書いたが、「Ruby3 では文字列をデフォルトで immutable にはしない」という決定がMatzによってなされた。
So I officially abandon making frozen-string-literals default (for Ruby3).
via. Feature #11473: Immutable String literal in Ruby 3 - Ruby master - Ruby Issue Tracking System
したがって、Ruby3以降も文字列を immutable にしたければ、引き続きfrozen_string_literal: true
のマジックコメントが必要となる。
Immutable String in Ruby2.3+
実は Ruby2.3 で既にこの Immutable String を有効にする機能が入っている。やり方はRubyファイルの行頭に次のようにfrozen_string_literal: true
とマジックコメントを書けばよい。
# frozen_string_literal: true
frozen_string = "This string is frozen!"
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
つまりfrozen_string_literal
が書かれた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)
方法1: String#dup
この場合の対処法としてはfreezeを解除したい文字列に対して、.dup
を付けてやれば解決する。
# frozen_string_literal: true
str = "a".dup
str << "bc"
puts str
# => abc
方法2: String#+@
あるいは、String#+@
を使って下記のようにも書ける。
# frozen_string_literal: true
str = +"a"
str << "bc"
puts str
# => abc
こちらのほうがdup
するよりもパフォーマンスが優れているので、こちらの書き方のほうがベターである。
まとめ
Ruby3 の Immutable String に先駆けて、Ruby2.3 以上が前提の実行環境では、積極的にfrozen_string_literal: true
のマジックコメント設定をしていくべき。