普段仕事で使っているRuby on Railsですが、ソースコードを読む機会もなかなかないので、試しにやってみることにしました
読めるようにするまで
以前書いた記事に読めるようにするまでの設定を画像キャプチャ付きで解説しましたので、よろしければこちらをご参照下さい
shitake4.hatenablog.com
読んだ箇所
Active Support コア拡張機能を見ていて知った duplicable?
を今日は読んでみようと思います
どんな使い方だっけ?
Railsを読んでみる前にまずは使い方を調べてみます
Active Support コア拡張機能の2.3 duplicable?を読んでみると
Rubyにおける基本的なオブジェクトの一部はsingletonオブジェクトです。たとえば、プログラムのライフサイクルが続く間、整数の1は常に同じインスタンスを参照します。
1.object_id # => 3
Math.cos(0).to_i.object_id # => 3従って、このようなオブジェクトはdupメソッドやcloneメソッドで複製することはできません。
true.dup # => TypeError: can't dup TrueClass
singletonでない数字にも、複製不可能なものがあります。
0.0.clone # => allocator undefined for Float
(2**1024).clone # => allocator undefined for BignumActive Supportには、オブジェクトがプログラム的に複製可能かどうかを問い合わせるためのduplicable?メソッドがあります。
"foo".duplicable? # => true
"".duplicable? # => true
0.0.duplicable? # => false
false.duplicable? # => falseデフォルトでは、nil、false、true、シンボル、数値、クラス、モジュール、メソッドオブジェクトを除くすべてのオブジェクトがduplicable? #=> trueです。
引用:Active Support コア拡張機能:2.3 duplicable?
dupメソッドやcloneメソッドで複製出来ない値が存在しているので、複製可能か確認する為の機能のようです
ソースコードを読んでみる
1. railsプロジェクトのactivesupportにある機能なので、activesupportディレクトリのlib配下で def duplicable?
を探してみます
2. 該当箇所が10個ほどあったので、それぞれみてみます
activesupport > lib > active_support > core_ext > object > duplicable.rb
Object.duplicable?
class Object # Can you safely dup this object? # # False for method objects; # true otherwise. def duplicable? true end end
上記箇所のコミットログを見てみると
Ruby2.4以前では NilClass, FalseClass, TrueClass, Symbol, Numeric
はdup出来ませんでしたが、2.4から可能となったようです
処理がどう変わったのか知りたいので、Ruby2.4.0のリファレンスを見てみます
オブジェクトの複製を作成して返します。
dup はオブジェクトの内容, taint 情報をコピーし、 clone はそれに加えて freeze, 特異メソッドなどの情報も含めた完全な複製を作成します。
clone や dup は浅い(shallow)コピーであることに注意してください。後述。
TrueClass, FalseClass, NilClass, Symbol, そして Numeric クラスのインスタンスなど一部のオブジェクトは複製ではなくインスタンス自身を返します。
[PARAM] freeze:
false を指定すると freeze されていないコピーを返します。
[EXCEPTION] ArgumentError:
TrueClass などの常に freeze されているオブジェクトの freeze されていないコピーを作成しようとしたときに発生します。
引用:Ruby 2.4.0 リファレンスマニュアル:Object#clone
NilClass, FalseClass, TrueClass, Symbol, Numeric
が複製ではなくインスタンス自身を返すようになったようです
合わせて以前の挙動はどうだったのか、Ruby2.3.0のリファレンスを見てみます
オブジェクトの複製を作成して返します。
dup はオブジェクトの内容, taint 情報をコピーし、 clone はそれに加えて freeze, 特異メソッドなどの情報も含めた完全な複製を作成します。
clone や dup は浅い(shallow)コピーであることに注意してください。後述。
[EXCEPTION] TypeError:
TrueClass, FalseClass, NilClass, Symbol, そして Numeric クラスのインスタンスなど一部のオブジェクトを複製しようとすると発生します。
引用:Ruby 2.3.0 リファレンスマニュアル:Object#clone
Ruby2.3.0のバージョンだとレシーバが NilClass, FalseClass, TrueClass, Symbol, Numeric
の場合にTypeErrorが発生するという仕様だったんですね
該当コミット
github.com
NilClass.duplicable?
class NilClass begin nil.dup rescue TypeError # +nil+ is not duplicable: # # nil.duplicable? # => false # nil.dup # => TypeError: can't dup NilClass def duplicable? false end end end
Ruby2.4.0からレシーバ NilClass, FalseClass, TrueClass, Symbol, Numeric
に対して dup/cloneが複製ではなくインスタンス自身を返すようになったため、オープンクラスする際にdupがTypeErrorを起こす(Ruby2.3.x以前のバージョンを使用している)場合に限りfalseを返すというようにしています
FalseClass.duplicable?
class FalseClass begin false.dup rescue TypeError # +false+ is not duplicable: # # false.duplicable? # => false # false.dup # => TypeError: can't dup FalseClass def duplicable? false end end end
こちらも同様です
TrueClass.duplicable?
class TrueClass begin true.dup rescue TypeError # +true+ is not duplicable: # # true.duplicable? # => false # true.dup # => TypeError: can't dup TrueClass def duplicable? false end end end
こちらも同様です
Numeric.duplicable?
class Numeric begin 1.dup rescue TypeError # Numbers are not duplicable: # # 3.duplicable? # => false # 3.dup # => TypeError: can't dup Integer def duplicable? false end end end
こちらも同様です
Complex.duplicable?
class Complex begin Complex(1).dup rescue TypeError # Complexes are not duplicable: # # Complex(1).duplicable? # => false # Complex(1).dup # => TypeError: can't copy Complex def duplicable? false end end end
こちらも同様です
Rational.duplicable?
class Rational begin Rational(1).dup rescue TypeError # Rationals are not duplicable: # # Rational(1).duplicable? # => false # Rational(1).dup # => TypeError: can't copy Rational def duplicable? false end end end
こちらも同様です
BigDecimal.duplicable?
require "bigdecimal" class BigDecimal # BigDecimals are duplicable: # # BigDecimal.new("1.2").duplicable? # => true # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> def duplicable? true end end
Objectと同様で常にtrueを返すようです
Method.duplicable?
class Method # Methods are not duplicable: # # method(:puts).duplicable? # => false # method(:puts).dup # => TypeError: allocator undefined for Method def duplicable? false end end
Methodは常にfalseを返すようです
Symbol.duplicable?
class Symbol begin :symbol.dup # Ruby 2.4.x. "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0. rescue TypeError # Symbols are not duplicable: # # :my_symbol.duplicable? # => false # :my_symbol.dup # => TypeError: can't dup Symbol def duplicable? false end end end
beginからrescueまでのコードを見てみると Ruby 2.4.0において特定のシンボルはdup出来ないようです
詳しい内容が気になったのでコミットログから探してみます
Githubで探してみると
.to_sym.dup
するとダメな文字列があったりするようです
該当コミット
github.com
読んでみて
普段使ってなかったメソッドだったのですが、調べてみることで、詳細な仕様が把握できたため、たまに使う時などにとてもハマるということはなくなりそうだなと思いました