【一人 bugs.ruby Advent Calendar 2020】[Feature #15504] Freeze all Range object【16日目】
一人 bugs.ruby Advent Calendar 2020 16日目の記事になります。
[Feature #15504] Freeze all Range object
リテラルで Range
を生成すると同じ id
のオブジェクトが返ってくるケースがあります。
# これは個別の id が返ってくる p (1..3).__id__ # => 60 p (1..3).__id__ # => 80 p (1..3).__id__ # => 100 def hoge (1..3).__id__ end # これは同じ id が返ってくる p hoge # => 120 p hoge # => 120 p hoge # => 120
これの影響で次のようなコードは奇妙な動作になります。
2.times{ r = (1..3) p r.instance_variable_get(:@foo) #=> 1回目は nil が返る #=> 2回目は :bar が返る r.instance_variable_set(:@foo, :bar) }
これは1回目の呼び出しの r.instance_variable_set(:@foo, :bar)
が2回目の呼び出しでも反映されているためです。
他にも Ractor との兼ね合いもあって Range リテラルは暗黙的に freeze
されるようになりました。
p (1..3).frozen? # => true p (1..3).frozen? # => true p (1..3).frozen? # => true def hoge (1..3).frozen? end p hoge # => true p hoge # => true p hoge # => true
2.times{ r = (1..3) p r.instance_variable_get(:@foo) # error: `instance_variable_set': can't modify frozen Range: 1..3 (FrozenError) r.instance_variable_set(:@foo, :bar) }
これは地味に影響がありそうな気がするんですけどどうなんですかね。今の所既存のコードが壊れた、みたいな報告は見ていませんが…。
これは補足ですが frozen
された Range
でも次のように中の値が変わってしまうことがあるので注意する必要があります。
r = ('a'..'z').freeze r.end.upcase! p r # => "a".."Z"