if var.nil?

If an if‘s condition is var.nil? then the type of var in the then branch is known by the compiler to be Nil, and to be known as non-Nil in the else branch:

  1. a = some_condition ? nil : 3
  2. if a.nil?
  3. # here a is Nil
  4. else
  5. # here a is Int32
  6. end

Instance Variables

Type restriction through if var.nil? only occurs with local variables. The type of an instance variable in a similar code example to the one above will still be nilable and will throw a compile error since greet expects a String in the unless branch.

  1. class Person
  2. property name : String?
  3. def greet
  4. unless @name.nil?
  5. puts "Hello, #{@name.upcase}" # Error: undefined method 'upcase' for Nil (compile-time type is (String | Nil))
  6. else
  7. puts "Hello"
  8. end
  9. end
  10. end
  11. Person.new.greet

You can solve this by storing the value in a local variable first:

  1. def greet
  2. name = @name
  3. unless name.nil?
  4. puts "Hello, #{name.upcase}" # name will be String - no compile error
  5. else
  6. puts "Hello"
  7. end
  8. end

This is a byproduct of multi-threading in Crystal. Due to the existence of Fibers, Crystal does not know at compile-time whether the instance variable will still be non-Nil when the usage in the if branch is reached.