nil and object identity

nil is an object too

nil is not the absence of a value; it is a value of class NilClass, and it answers methods. That is why a chain like x.nil? works even when x is missing, and why the error you get from nil.upcase is a NoMethodError rather than a syntax error.

ruby -e 'x = nil; puts x.nil?; puts x.inspect; p nil.to_a'

true
nil
[]

inspect prints nil for a reader, to_a gives an empty array, and to_s gives an empty string. A method that returns nil is not broken; it is saying "no value", and the caller decides what to do.

equal? is identity

Two strings with the same characters are equal values and two different objects. equal? asks whether two names point at one object, and it is almost never the method a program wants:

ruby -e 'puts "a".object_id == "a".object_id'

false

ruby -e 'a = "x"; b = "x"; puts a.equal?(b); puts a == b'

false
true

The two literals are separate objects with separate ids, which is why the first line prints false. A variable assignment copies the reference, so c = a; c.equal?(a) is true for any value, strings included.

== and eql?

== asks "same value" and is the one a program uses. eql? is the stricter form that a hash uses to find a key: it also compares the class.

ruby -e 'puts 1.eql?(1.0); puts 1 == 1.0'

false
true

So 1 and 1.0 are equal with == and different with eql?. A hash keyed by 1 does not find the value stored under 1.0, and a test that uses assert_equal passes for the pair because it uses ==.

What to watch for

Do not compare object ids. An id is unique for the life of an object and is not stable across runs; a test that records one breaks on the next run. Compare values with == and, when the type matters, compare class as well.

A class that defines == should define eql? and hash with it, or two equal objects behave differently in an array and in a hash.

Next: Mutable, frozen and copied objects

Article author: Arthur Isaev