コンテンツにスキップ

if var

変数がifの条件式に使われたとき、then節の中ではその変数はNil型を持たないと判断されます。

a = some_condition ? nil : 3
# a is Int32 or Nil

if a
  # ここに到達するためには if が真でなければならない
  # ということは、a  が nil というのはあり得ないので、ここでは必ず Int32 である
  a.abs
end

ifの条件式中で変数への代入が行われた場合にも、この判断がなされます。

if a = some_expression
  # ここでは a は nil ではない
end

条件式でかつ (&&) が使われた場合にも同様です。

if a && b
  # ここでは a も b も Nil ではないことが保証される
end

&& 式の右辺が評価された場合、aNilではないことも同様に保証されています。

もちろん、then節の中で変数へ再代入を行なった場合は、その代入された式に応じて変数の型が変わります。

制約

これまで書いてきたことはローカル変数に対してのみ機能します。インスタンス変数、クラス変数、クロージャに束縛された変数に関してはこの機能は機能しません。なぜならこれらの変数の値は別のファイバーによってnilに変更される可能性があるからです。また、定数に対しても機能しません。

if @a
  # ここでも `@a` が nil の可能性がある
end

if @@a
  # ここでも `@@a` が nil の可能性がある
end

a = nil
closure = ->{ a = "foo" }

if a
  # ここでも `a` が nil の可能性がある
end

この制約は値をローカル変数に代入することで回避できます。

if a = @a
  # ここで `a` は nil にはならない
end

他の手法として、標準ライブラリのObject#tryメソッドを使うこともできます。これはnilでない場合にのみブロックを実行する、というメソッドです。

@a.try do |a|
  # ここで `a` は nil にはならない
end

メソッド呼び出し

Proc やメソッドの呼び出し (ゲッターやプロパティも含む) の場合にも当てはまりません。なぜなら、Nil を許容する (もしくは、複数の型の組み合わせとなるユニオン型の場合がより一般的でしょう) Proc やメソッドの呼び出しの場合、連続した呼び出しであっても、それらが常に同じ型を返すとは限らないからです。

if method # 最初の呼び出しでメソッドは Int32 か Nil を返す
  # ここで、最初の呼び出しが Nil を返していないことはわかっている
  method # 2度目の呼び出しでも Int32 か Nil を返す
end

こういった Proc やメソッド呼び出しの場合にも、上記でインスタンス変数に関して記載したテクニックが有効です。