Search code examples
crystal-lang

Null Type filtering on nested Crystal object


I get an

undefined method 'start_time' for Nil (compile-time type is (Reservation | Nil))

for the code

 if game.reservation && other_game.reservation
       if(game.reservation.start_time == other_game.reservation.start_time)                    
            return false 
       end
 end

But if I do this

reservation : Reservation | Nil = game.reservation
other_reservation : Reservation | Nil = other_game.reservation
if reservation && other_reservation
    if(reservation.start_time == other_reservation.start_time)                    
        return false 
    end
end

Why aren't these expressions equivalent? Normally, the if is a type filter that removes the Nil union from the type, but not when it is a nested object. The second way works, but feels needlessly verbose.

What's the right way to perform a type filter with an if on a nested object?


Solution

  • Let's simplify this a bit (it's still the same error):

    if game.reservation
      game.reservation.starting_time
    end
    

    The conditional ensures that the return value from game.reservation is not nil. This expression is just calling the method reservation on game. The returned value is not reused after that and there is no way of knowing if the second call to that same method might return nil.

    You can solve this easily by storing the return value in a local variable. This way the compiler can be sure that it's value is not nil. This is even more performant because it saves an (potentially costly) additional call to the same method.

    if reservation = game.reservation
      reservation.starting_time
    end
    

    The exact behaviour is explained in more detail in the language reference: https://crystal-lang.org/docs/syntax_and_semantics/if_var.html