Ruby
プロを目指す人のためのRuby入門

はじめに

この記事は書籍「プロを目指す人のためのRuby入門」に掲載できなかったトピックを著者自らが紹介するアドベントカレンダーの13日目です。
本文に出てくる章番号や項番号は書籍の中で使われている番号です。

今回は文字列をfreezeさせるいくつかの方法を紹介していきます。

必要な前提知識

「プロを目指す人のためのRuby入門」の第7章まで読み終わっていること。

文字列をfreezeさせるいくつかの方法

freezeについておさらい

Rubyの文字列はミュータブル、つまり破壊的な変更が可能なオブジェクトです(ミュータブル、イミュータブルという用語については4.7.14項を参照)。

a = 'hello'
# 文字列を破壊的に変更する
a.upcase!
# 元の文字列が変更される
a #=> "HELLO"

破壊的な変更を防止したい場合はfreezeを呼び出します(7.8.1項参照)。

a = 'hello'

# 初期状態ではfreezeしていない
a.frozen? #=> false

# 文字列をfreezeさせる
a.freeze
a.frozen? #=> true

# freezeされると破壊的な変更ができない(エラーになる)
a.upcase! #=>RuntimeError: can't modify frozen String

なお、freezeメソッドはObjectクラスのメソッドなので、文字列以外のオブジェクトでも呼び出すことが可能です。

マジックコメントでファイル全体の文字列をfreezeさせる

Ruby 2.3からはfrozen_string_literalというマジックコメントが導入されました。
# frozen_string_literal: trueというマジックコメントをファイルの先頭に書いておくと、文字列が最初からfreezeされます(以下のコードはirbではなく、適当なファイルに保存してから実行してください)。

sample.rb
# frozen_string_literal: true

a = 'hello'

# 最初からfreezeしている
puts a.frozen? #=> true

a.upcase! #=>RuntimeError: can't modify frozen String

ただし、この方法でfreezeされるのは「文字列リテラル」に限定される点に注意してください(リテラルについては2.2.7項を参照)。to_sメソッドで数値を文字列に変換したような場合は、文字列リテラルが使われていないのでfreezeされません。

sample.rb
# frozen_string_literal: true

# 以下はいずれも文字列リテラルなのでfreezeされる
puts 'hello'.frozen?   #=> true

puts %q!hello!.frozen? #=> true

puts <<TEXT.frozen?    #=> true
hello
TEXT

# 数値を文字列に変換したような場合はfreezeされない
puts 123.to_s.frozen? #=> false

単項演算子の-でfreezeさせる

Ruby 2.3からは文字列の単項演算子として-が追加されました。
単項演算子の-を使うと、freezeされた文字列が返ります。

# 単項演算子の-でfreezeした文字列を返す
a = -'hello'
a.frozen? #=> true

ただし、-を使った場合は、freezeされた新しい文字列が返るだけで、元の文字列はfreezeされません。

b = 'bye'

# freezeした新しい文字列を代入する
c = -b
c.frozen? #=> true

# 元の文字列はfreezeされない
b.frozen? #=> false

反対に、frozen_string_literalで文字列リテラルがfreezeされる場合は、単項演算子の+を使うことでfreezeしていない文字列を作成することもできます。

sample.rb
# frozen_string_literal: true

# 通常は最初からfreezeされる
a = 'hello'
puts a.frozen? #=> true

# 単項演算子の+を使うとfreezeされない
# (厳密にはfreezeされている'bye'のfreezeされていない複製が返される)
b = +'bye'
puts b.frozen? #=> false

議論:どこまで積極的にfreezeすべきか?

将来リリースされるRuby 3ではパフォーマンスを向上させるために、文字列リテラルがデフォルトでイミュータブルになる(つまりfreezeされる)と言われています。frozen_string_literalというマジックコメントが導入されたのはRuby 3への移行を支援するためです。

このあたりのいきさつや議論についての詳細は以下のリンクを参照してください。

将来的にはデフォルトでイミュータブルになるとはいえ、今から積極的にその対応を進めるべきかどうかは人によって(もしくは企業や開発チームによって)判断が分かれるところだと思います。たとえば、以下の記事のように、開発チームによってはfrozen_string_literalを積極的に付けるようにしているところもあるようです。

今後も長く運用・保守されていくことが明らかなRubyプログラムやRailsアプリケーションであれば、frozen_string_literalを積極的に付けることで、Ruby 3への対応工数を削減できる効果が期待できます。

一方、使い捨てのRubyプログラムや小さなサンプルコードであれば、frozen_string_literalをわざわざ付けるメリットは少ないと思います。「プロを目指す人のためのRuby入門」のサンプルコードもこちらに該当するので、書籍内ではfrozen_string_literalは付けていません。

このように、今から積極的にfreezeさせる必要があるかどうかは状況によって変わってくるため、開発チーム内で方針を議論して適切な対応を取るようにしてください。

次回予告

次回は正規表現関連の便利メソッドを紹介します。