17
votes

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 
4

4 Answers

12
votes

I checked the source and here's what I found out:

The comparison operators defined by Comparable all use the function rb_cmpint together with <=>. rb_cmpint raises 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 < 2 is false, but 5 < "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: When x < y is false that implies that x >= y is 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 an rb_rescue. And that returns nil if an exception is thrown.

Note that this only applies to ruby 1.8. This has been fixed in 1.9 and now == never returns nil (except of course if you define your own == that does).

7
votes

If you're depending on this for code, you can always use the .nil? method which any Ruby Object responds to.

>> Date.new.nil?
=> false
4
votes

The Date class includes the Comparable#== method, but that method invokes the <=> method of the receiver. In this case that's Date#<=>, which expects another Date object. When it receives nil it returns nil. This behavior certainly seems inconsistent, and I don't know the reasons behind it.

0
votes

It happens because you can't compare things that are not defined. It's desirable because if at least one of your operands is not defined then you can't draw any conclusion about the result, which is different from asserting truth.

A lot of languages treat nil and false the same, which is suspect is purely for convenience. It's certainly not mathematically correct.