When writing some rspec today, I came across some unexpected behavior with comparing Date (and Time) instances to nil. Here’s a sample using raw ruby (no Rails or other libraries):
user@MacBook-Work ~ $ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]
user@MacBook-Work ~ $ irb
>> 1 == nil
=> false
>> "string" == nil
=> false
>> :sym == nil
=> false
>> false == nil
=> false
>> [] == nil
=> false
>> {} == nil
=> false
>> Proc.new {} == nil
=> false
So far, so good, right?
>> Date.new == nil
=> nil
>> Time.new == nil
=> nil
Date does implement its own ===, which works fine:
>> Date.new === nil
=> false
Is there any explanation as to why this happens or why this is desired behavior? == seems to be implemented from Comparable.==, however documentation on that doesn’t given any indication that it would ever return nil. What’s the design decision to this?
Update! This is not the case in 1.9.2:
$ irb
ruby-1.9.2-p136 :001 > require 'date'
=> true
ruby-1.9.2-p136 :002 > Date.new == nil
=> false
ruby-1.9.2-p136 :003 > Time.new == nil
=> false
I checked the source and here’s what I found out:
The comparison operators defined by Comparable all use the function
rb_cmpinttogether with<=>.rb_cmpintraises an exception when one of the operands is nil.So the operators of Comparable raise an exception if the rhs is not comparable to the lhs. I.e.
5 < 2is false, but5 < "la"raises an exception. They do this to differentiate between cases where<is not true because the rhs is smaller and cases where it’s not true because the rhs is not comparable. Or in other words: Whenx < yis false that implies thatx >= yis true. So in cases where that would not be the case, it throws an exception.==raising an exception would be bad, because==usually does not (and should not) require its operands to be comparable. However==uses the same method as the other operands, which does raise an exception. So the whole function is simply wrapped in anrb_rescue. And that returnsnilif an exception is thrown.Note that this only applies to ruby 1.8. This has been fixed in 1.9 and now
==never returnsnil(except of course if you define your own==that does).