Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

RubyのHashとArrayをdeep freezeするfreeezerというgemを書きました

RubyでHashとArrayをdeep freezeするための, freeezerというgemを書きました.

そもそもRubyのfreezeとは?

Rubyのfreezeはオブジェクトへの破壊的な変更を防ぐためのメソッドで, よく定数の定義とセットで使われます.

Rubyはデフォルトで定数への破壊的変更を許可しているため, 例えば以下のような現象が起こりえます.

class Test
  SOME_CONST = 'some const'
end

Test::SOME_CONST.upcase!
Test::SOME_CONST # => 'SOME CONST'

これを防ぐために,定数にイミュータブルなオブジェクトを代入する場合は freeze して破壊的変更を禁止する場合があるわけです.

class Test
  SOME_CONST = 'some const'.freeze
end

Test::SOME_CONST.upcase!
Test::SOME_CONST # => raise RuntimeError: can't modify frozen String

ArrayやHashオブジェクトの場合は中身までfreezeする必要がある

Stringをfreezeしたい場合などは上記の通り単純にfreezeすればいいのですが, ArrayやHashオブジェクトをfreezeしたい場合にはもう少し注意になります.

例えば以下のようにArrayをfreezeしてもArrayの参照しているオブジェクトまではfreezeされません.

SOME_ARRAY = ['one', 'two', 'three'].freeze

# Arrayへの破壊的変更は禁止される
SOME_ARRAY.push('four') # => raise RuntimeError: can't modify frozen Array

# Arrayが参照しているオブジェクトへの破壊的変更は許可される
SOME_ARRAY[0].upcase!
SOME_ARRAY # => ['ONE', 'two', 'three']

もし本当にArrayオブジェクトの参照しているオブジェクトまでイミュータブルにしたければ,以下のように各オブジェクトもfreezeする必要があります.

SOME_ARRAY = ['one'.freeze, 'two'.freeze, 'three'.freeze].freeze
SOME_ARRAY[0].upcase! # => raise RuntimeError: can't modify frozen String

というわけでこれを一発でできるのがfreeezerというgem

壮大な前フリになってしまいましたが, 要するにArrayとHashの中身まで一発でイミュータブルにできるのがfreeezerというgemです.

using Freeezer
SOME_ARRAY = ['one', 'two', 'three'].deep_freeze

SOME_ARRAY.push('four') # => raise RuntimeError: can't modify frozen Array
SOME_ARRAY[0].upcase! # => raise RuntimeError: can't modify frozen String

上記のように using Freezer を呼んでもらえれば deep_freeze ※1 というメソッド一発で再帰的に中のオブジェクトまでfreezeしてくれます.

所感など

自分で書いておいてなんですが, 正直クラスにある1つか2つの定数をfreezeするためにこのgemを入れて using Freeeze って書くかと言われるとちょっと微妙かなーという気はしています.

あとたぶん定数でStringのArrayとかHashを使いたい場合がStringじゃなくてSymbolで定義すればいいのではという気もしています(Symbolはイミュータブル).

なのでもし使うとしたら Constants みたいな定数をまとめて定義しているファイルがプロジェクトにあって,そこで何度も中身をfreezeする処理を書く場合にはいいのかもしれません.

また,今回の deep_freeze の元ネタはプロを目指すためのRuby入門 の8章 "モジュールを理解する" で例題として紹介されていた deep_freeze です. こいつをもう少し真面目に書いたらどうなるかということをやってみたくてgemを作ってみました.

※1 本当は deep_freeze じゃなくて freeeze にしたかったんですが, 流石に怒られそうだし, deep_freeze のほうが意味が伝わりやすいので断念しました.

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?