74

Is it better to use obj.nil? or obj == nil and what are the benefits of both?

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
Danish Khan
  • 1,230
  • 1
  • 10
  • 13

7 Answers7

52

Is it better to use obj.nil? or obj == nil

It is exactly the same. It has the exact same observable effects from the outside ( pfff ) *

and what are the benefits of both.

If you like micro optimizations all the objects will return false to the .nil? message except for the object nil itself, while the object using the == message will perform a tiny micro comparison with the other object to determine if it is the same object.

* See comments.

OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • "It is exactly the same." Unless someone's [monkeypatched](http://avdi.org/devblog/2011/05/30/null-objects-and-falsiness/) `nil?` and not `==`, or vice versa. Then again, just because you can doesn't mean you should... – Andrew Grimm Jul 05 '11 at 07:33
  • 6
    *"Exactly the same"?* Hmm. One calls the `#nil?` method inherited from Object which in a sane world would return true only for `NilClass`, and one compares an object against the `nil` singleton. Not *exactly* the same. Barring lunatic fringe overriding of `nil?`, it should produce *the same resulting truth value* but even then, imagine someone deciding to patch `FalseClass` so that `nil?` is true... – DigitalRoss Jul 05 '11 at 16:05
  • 2
    @Andrew @DigitalRoss Ok, ok, I changed phrase and referred to your comments :) Some people argue this is the main problem with Ruby; for a large project you have to add test cases to test none does one of those silly things. – OscarRyz Jul 05 '11 at 16:48
14

Syntax and style aside, I wanted to see how "the same" various approaches to testing for nil were. So, I wrote some benchmarks to see, and threw various forms of nil testing at it.

TL;DR - Results First

The actual results showed that using obj as a nil check is the fastest in all cases. obj is consistently faster by 30% or more than checking obj.nil?.

Surprisingly, obj performs about 3-4 times as fast as variations on obj == nil, for which there seems to be a punishing performance penalty.

Want to speed up your performance-intensive algorithm by 200%-300%? Convert all obj == nil checks to obj. Want to sandbag your code's performance? Use obj == nil everywhere that you can. (just kidding: don't sandbag your code!).

In the final analysis, always use obj. That jives with the Ruby Style Guide rule: Don't do explicit non-nil checks unless you're dealing with boolean values.

The Benchmark Conditions

OK, those are the results. So how is this benchmark put together, what tests were done, and what are the details of the results?

The nil checks that I came up with are:

  • obj
  • obj.nil?
  • !obj
  • !!obj
  • obj == nil
  • obj != nil

I picked various Ruby types to test, in case the results changed based on the type. These types were Fixnum, Float, FalseClass, TrueClass, String, and Regex.

I used these nil check conditions on each of the types to see if there was a difference between them, performance-wise. For each type, I tested both nil objects and non-nil value objects (e.g. 1_000_000, 100_000.0, false, true, "string", and /\w/) to see if there's a difference in checking for nil on an object that is nil versus on an object that's not nil.

The Benchmarks

With all of that out of the way, here is the benchmark code:

require 'benchmark'

nil_obj = nil
N = 10_000_000

puts RUBY_DESCRIPTION

[1_000_000, 100_000.0, false, true, "string", /\w/].each do |obj|
  title = "#{obj} (#{obj.class.name})"
  puts "============================================================"
  puts "Running tests for obj = #{title}"

  Benchmark.bm(15, title) do |x|
    implicit_obj_report   = x.report("obj:")            { N.times { obj            } }
    implicit_nil_report   = x.report("nil_obj:")        { N.times { nil_obj        } }
    explicit_obj_report   = x.report("obj.nil?:")       { N.times { obj.nil?       } }
    explicit_nil_report   = x.report("nil_obj.nil?:")   { N.times { nil_obj.nil?   } }
    not_obj_report        = x.report("!obj:")           { N.times { !obj           } }
    not_nil_report        = x.report("!nil_obj:")       { N.times { !nil_obj       } }
    not_not_obj_report    = x.report("!!obj:")          { N.times { !!obj          } }
    not_not_nil_report    = x.report("!!nil_obj:")      { N.times { !!nil_obj      } }
    equals_obj_report     = x.report("obj == nil:")     { N.times { obj == nil     } }
    equals_nil_report     = x.report("nil_obj == nil:") { N.times { nil_obj == nil } }
    not_equals_obj_report = x.report("obj != nil:")     { N.times { obj != nil     } }
    not_equals_nil_report = x.report("nil_obj != nil:") { N.times { nil_obj != nil } }
  end
end

The Results

The results were interesting, because Fixnum, Float, and String types performance was virtually identical, Regex nearly so, and FalseClass and TrueClass performed much more quickly. Testing was done on MRI versions 1.9.3, 2.0.0, 2.1.5, and 2.2.5 with very similar comparative results across the versions. The results from the MRI 2.2.5 version are shown here (and available in the gist:

ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin14]
============================================================
Running tests for obj = 1000000 (Fixnum)
                      user     system      total        real
obj:              0.970000   0.000000   0.970000 (  0.987204)
nil_obj:          0.980000   0.010000   0.990000 (  0.980796)
obj.nil?:         1.250000   0.000000   1.250000 (  1.268564)
nil_obj.nil?:     1.280000   0.000000   1.280000 (  1.287800)
!obj:             1.050000   0.000000   1.050000 (  1.064061)
!nil_obj:         1.070000   0.000000   1.070000 (  1.170393)
!!obj:            1.110000   0.000000   1.110000 (  1.122204)
!!nil_obj:        1.120000   0.000000   1.120000 (  1.147679)
obj == nil:       2.110000   0.000000   2.110000 (  2.137807)
nil_obj == nil:   1.150000   0.000000   1.150000 (  1.158301)
obj != nil:       2.980000   0.010000   2.990000 (  3.041131)
nil_obj != nil:   1.170000   0.000000   1.170000 (  1.203015)
============================================================
Running tests for obj = 100000.0 (Float)
                      user     system      total        real
obj:              0.940000   0.000000   0.940000 (  0.947136)
nil_obj:          0.950000   0.000000   0.950000 (  0.986488)
obj.nil?:         1.260000   0.000000   1.260000 (  1.264953)
nil_obj.nil?:     1.280000   0.000000   1.280000 (  1.306817)
!obj:             1.050000   0.000000   1.050000 (  1.058924)
!nil_obj:         1.070000   0.000000   1.070000 (  1.096747)
!!obj:            1.100000   0.000000   1.100000 (  1.105708)
!!nil_obj:        1.120000   0.010000   1.130000 (  1.132248)
obj == nil:       2.140000   0.000000   2.140000 (  2.159595)
nil_obj == nil:   1.130000   0.000000   1.130000 (  1.151257)
obj != nil:       3.010000   0.000000   3.010000 (  3.042263)
nil_obj != nil:   1.170000   0.000000   1.170000 (  1.189145)
============================================================
Running tests for obj = false (FalseClass)
                      user     system      total        real
obj:              0.930000   0.000000   0.930000 (  0.933712)
nil_obj:          0.950000   0.000000   0.950000 (  0.973776)
obj.nil?:         1.250000   0.000000   1.250000 (  1.340943)
nil_obj.nil?:     1.270000   0.010000   1.280000 (  1.282267)
!obj:             1.030000   0.000000   1.030000 (  1.039532)
!nil_obj:         1.060000   0.000000   1.060000 (  1.068765)
!!obj:            1.100000   0.000000   1.100000 (  1.111930)
!!nil_obj:        1.110000   0.000000   1.110000 (  1.115355)
obj == nil:       1.110000   0.000000   1.110000 (  1.121403)
nil_obj == nil:   1.100000   0.000000   1.100000 (  1.114550)
obj != nil:       1.190000   0.000000   1.190000 (  1.207389)
nil_obj != nil:   1.140000   0.000000   1.140000 (  1.181232)
============================================================
Running tests for obj = true (TrueClass)
                      user     system      total        real
obj:              0.960000   0.000000   0.960000 (  0.964583)
nil_obj:          0.970000   0.000000   0.970000 (  0.977366)
obj.nil?:         1.260000   0.000000   1.260000 (  1.265229)
nil_obj.nil?:     1.270000   0.010000   1.280000 (  1.283342)
!obj:             1.040000   0.000000   1.040000 (  1.059689)
!nil_obj:         1.070000   0.000000   1.070000 (  1.068290)
!!obj:            1.120000   0.000000   1.120000 (  1.154803)
!!nil_obj:        1.130000   0.000000   1.130000 (  1.155932)
obj == nil:       1.100000   0.000000   1.100000 (  1.102394)
nil_obj == nil:   1.130000   0.000000   1.130000 (  1.160324)
obj != nil:       1.190000   0.000000   1.190000 (  1.202544)
nil_obj != nil:   1.200000   0.000000   1.200000 (  1.200812)
============================================================
Running tests for obj = string (String)
                      user     system      total        real
obj:              0.940000   0.000000   0.940000 (  0.953357)
nil_obj:          0.960000   0.000000   0.960000 (  0.962029)
obj.nil?:         1.290000   0.010000   1.300000 (  1.306233)
nil_obj.nil?:     1.240000   0.000000   1.240000 (  1.243312)
!obj:             1.030000   0.000000   1.030000 (  1.046630)
!nil_obj:         1.060000   0.000000   1.060000 (  1.123925)
!!obj:            1.130000   0.000000   1.130000 (  1.144168)
!!nil_obj:        1.130000   0.000000   1.130000 (  1.147330)
obj == nil:       2.320000   0.000000   2.320000 (  2.341705)
nil_obj == nil:   1.100000   0.000000   1.100000 (  1.118905)
obj != nil:       3.040000   0.010000   3.050000 (  3.057040)
nil_obj != nil:   1.150000   0.000000   1.150000 (  1.162085)
============================================================
Running tests for obj = (?-mix:\w) (Regexp)
                      user     system      total        real
obj:              0.930000   0.000000   0.930000 (  0.939815)
nil_obj:          0.960000   0.000000   0.960000 (  0.961852)
obj.nil?:         1.270000   0.000000   1.270000 (  1.284321)
nil_obj.nil?:     1.260000   0.000000   1.260000 (  1.275042)
!obj:             1.040000   0.000000   1.040000 (  1.042543)
!nil_obj:         1.040000   0.000000   1.040000 (  1.047280)
!!obj:            1.120000   0.000000   1.120000 (  1.128137)
!!nil_obj:        1.130000   0.000000   1.130000 (  1.138988)
obj == nil:       1.520000   0.010000   1.530000 (  1.529547)
nil_obj == nil:   1.110000   0.000000   1.110000 (  1.125693)
obj != nil:       2.210000   0.000000   2.210000 (  2.226783)
nil_obj != nil:   1.170000   0.000000   1.170000 (  1.169347)
Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
11

Personally, I prefer object.nil? as it can be less confusing on longer lines; however, I also usually use object.blank? if I'm working in Rails as that also checks to see if the variable is empty.

Topher Fangio
  • 20,372
  • 15
  • 61
  • 94
5

In many cases, neither, just test the boolean truth value

Although the two operations are very different I'm pretty sure they will always produce the same result, at least until someone out on the edge of something decides to override Object's #nil? method. (One calls the #nil? method inherited from Object or overridden in NilClass and one compares against the nil singleton.)

I would suggest that when in doubt you go a third way, actually, and just test the truth value of an expression.

So, if x and not if x == nil or if x.nil?, in order to have this test DTRT when the expression value is false. Working this way may also help to avoiding tempting someone to define FalseClass#nil? as true.

DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
3

You can use Symbol#to_proc on nil?, whereas it wouldn't be practical on x == nil.

arr = [1, 2, 3]
arr.any?(&:nil?) # Can be done
arr.any?{|x| x == nil} # More verbose, and I suspect is slower on Ruby 1.9.2
! arr.all? # Check if any values are nil or false
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
0

I find myself not using .nil? at all when you can do:

unless obj
  // do work
end

It's actually slower using .nil? but not noticeably. .nil? is just a method to check if that object is equal to nil, other than the visual appeal and very little performance it takes there is no difference.

Garrett
  • 7,830
  • 2
  • 41
  • 42
-1

Some might suggest that using .nil? is slower than the simple comparison, which makes sense when you think about it.

But if scale and speed are not your concern, then .nil? is perhaps more readable.

ewall
  • 27,179
  • 15
  • 70
  • 84
  • 1
    In Ruby `nil?` and `==` are both instance methods. In that example, the performance gap was due to the useless call to .nil? method on a branch, but when you are checking an object for nil, IMHO it should be almost the same computation effort... – Andrea Salicetti Apr 23 '12 at 06:47