Railsのソースコード読んでみる | ActiveSupport duplicable?編

f:id:sktktk1230:20180726124729p:plain

普段仕事で使っている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 Bignum

Active 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? を探してみます

f:id:sktktk1230:20171218175426p:plain

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

上記箇所のコミットログを見てみると
f:id:sktktk1230:20171218180129p:plain

Ruby2.4以前では NilClass, FalseClass, TrueClass, Symbol, Numericdup出来ませんでしたが、2.4から可能となったようです
f:id:sktktk1230:20171218180531p:plain

処理がどう変わったのか知りたいので、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出来ないようです
詳しい内容が気になったのでコミットログから探してみます
f:id:sktktk1230:20171218180338p:plain

Githubで探してみると
f:id:sktktk1230:20171218175648p:plain

.to_sym.dup するとダメな文字列があったりするようです

該当コミット
github.com

読んでみて

普段使ってなかったメソッドだったのですが、調べてみることで、詳細な仕様が把握できたため、たまに使う時などにとてもハマるということはなくなりそうだなと思いました