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_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).
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With