コンテンツにスキップ

if var.nil?

if文の条件がvar.nil?の場合、thenブランチ内のvarの型はコンパイラーによってNilであると認識され、elseブランチでは非Nilであると認識されます。

a = some_condition ? nil : 3
if a.nil?
  # here a is Nil
else
  # here a is Int32
end

インスタンス変数

if var.nil?による型制約は、ローカル変数でのみ発生します。上記のコード例と同様のインスタンス変数の型は、依然としてnilableであり、unlessブランチでgreetStringを期待しているため、コンパイルエラーが発生します。

class Person
  property name : String?

  def greet
    unless @name.nil?
      puts "Hello, #{@name.upcase}" # Error: undefined method 'upcase' for Nil (compile-time type is (String | Nil))
    else
      puts "Hello"
    end
  end
end

Person.new.greet

これは、最初に値をローカル変数に格納することで解決できます。

def greet
  name = @name
  unless name.nil?
    puts "Hello, #{name.upcase}" # name will be String - no compile error
  else
    puts "Hello"
  end
end

これは、Crystalのマルチスレッドの副産物です。ファイバーが存在するため、Crystalは、ifブランチでの使用に到達したときに、インスタンス変数が依然として非Nilであるかどうかをコンパイル時に認識しません。