Skip to content

Only nil and false are falsy

Level: 101 · anyone who has written if count in Python and expects 0 to be false

One line: Ruby has exactly two falsy values, nil and false; 0, 0.0, "", [] and {} are all truthy and no class can opt in to falsiness — where Python asks __bool__, then __len__, and calls all six of those values false.

Ruby decides truthiness once, in the interpreter, by identity: an object is falsy if it is nil or false and truthy otherwise. There is no protocol to consult, so if 0, if "" and if [] all take the then branch, x || default replaces only nil and false, and a class that defines ! changes what !obj returns without changing what if obj does. Python asks the object: bool(x) calls x.__bool__(), falls back to len(x) == 0, and only then to "true". That one difference produces most of the surprises a Python programmer meets in a first week of Ruby, and most of the ones a Ruby programmer meets going the other way.

Verified output of nil_false_and_truthiness_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. the truth table: !!x
   nil                                  false
   false                                false
   0                                    true
   0.0                                  true
   ""                                   true
   []                                   true
   {}                                   true
   :a                                   true
2. nil is an object with methods
   nil.to_s                             ""
   nil.to_a                             []
   nil.to_i                             0
   nil.inspect                          "nil"
3. || returns the first truthy operand
   x = nil;   x || "default"            "default"
   x = 0;     x || "default"            0
   x = "";    x || "default"            ""
   x = false; x || "default"            "default"
4. nil and false are two different objects
   nil == false                         false
   nil.nil?                             true
   false.nil?                           false
   false == 0                           false
5. truthiness is not a method a class can define
   !h   (Hollow defines !)              true
   h ? :truthy : :falsy                 :truthy
   !!h  (calls ! twice)                 false
   [].empty?                            true
   [] ? :truthy : :falsy                :truthy
6. if 0 and if "" take the then branch
   n = 0;  n ? :then : :else            :then
   s = ""; s ? :then : :else            :then
7. dropping nil is not dropping falsy
   mixed.compact                        [1, false, 2]
   mixed.select(&:itself)               [1, 2]

Reading the output

  • Row 1. !!x is the idiom for "the truthiness of x, as a boolean". Only nil and false give false; 0, 0.0, the empty string, the empty array, the empty hash and a symbol all give true.
  • Row 2. nil is a real object. nil.to_s is the empty string, nil.to_a the empty array, nil.to_i zero, and nil.inspect is the text nil that p prints.
  • Row 3. || returns its first truthy operand, so x || "default" replaces nil and false and nothing else: 0 and "" survive. The ||= idiom rests on this row, and so does its one trap — a legitimate false gets replaced too.
  • Row 4. nil and false are different objects of different classes: nil == false is false, nil.nil? is the test that singles out nil, and false == 0 is false because false is not a number.
  • Row 5. Hollow defines ! to return true, so !h is true — but h ? :truthy : :falsy still says :truthy, because a conditional never calls !. The trap in the middle: !!h is ! applied twice, so with a custom ! it says false for an object every if treats as true. [] is empty? and truthy at the same time.
  • Row 6. if 0 and if "" run the then branch. Testing for "no items" in Ruby is if list.empty?, and for "no value" it is if x.nil?.
  • Row 7. compact drops nil only; select(&:itself) drops nil and false. Pick the one that says what you mean.

Compared with Python

Python's bool(x) disagrees with !!x on six of row 1's eight values: 0, 0.0, '', [] and {} are False along with None and False. That difference runs through every later row. x or "default" replaces 0 and "" too (row 3), which is why count = count or 10 is a Python bug when 0 is a legitimate count. False == 0 is True (row 4) — bool is a subclass of int, which the duck typing page prints as isinstance(True, int) — where Ruby's false == 0 is false. Truthiness is a protocol: Hollow.__bool__ makes h falsy in every position — not h, the conditional expression and not not h all agree (row 5) — whereas Ruby's ! override changed only !h. if 0 and if "" take the else branch (row 6). Rows 2 and 7 agree in shape: None is an object with str and repr, though without Ruby's forgiving to_a and to_i, and filtering out None is a different thing from filtering by truth in both languages.

Verified output of nil_false_and_truthiness_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. the truth table: bool(x)
   None                                 False
   False                                False
   0                                    False
   0.0                                  False
   ''                                   False
   []                                   False
   {}                                   False
   'a' (no symbols: a str)              True
2. None is an object with methods
   str(None)                            'None'
   list(None) (no nil.to_a)             TypeError
   int(None) (no nil.to_i)              TypeError
   repr(None)                           'None'
3. or returns the first truthy operand
   x = None;  x or "default"            'default'
   x = 0;     x or "default"            'default'
   x = "";    x or "default"            'default'
   x = False; x or "default"            'default'
4. None and False are two different objects
   None == False                        False
   x = None;  x is None                 True
   x = False; x is None                 False
   False == 0                           True
5. truthiness is a method a class can define
   not h   (Hollow defines __bool__)    True
   'truthy' if h else 'falsy'           'falsy'
   not not h                            False
   len([])                              0
   'truthy' if [] else 'falsy'          'falsy'
6. if 0 and if '' take the else branch
   n = 0;  'then' if n else 'else'      'else'
   s = ''; 'then' if s else 'else'      'else'
7. dropping None is not dropping falsy
   [v for v in mixed if v is not None]  [1, False, 2]
   [v for v in mixed if v]              [1, 2]

Python's rules are the Truth Value Testing section of the built-in types reference ↗; the Python library's glossary ↗ is the vocabulary on that side.

Try it

  1. Write blank?(x) that is true for nil, false, an empty or whitespace-only string, and an empty array or hash — the idea Rails calls blank? — and print a table of !!x beside blank?(x) for nil, false, 0, "", " ", [], {}, "a", [nil] and true. Where do the two columns disagree?
  2. A variable flag may legitimately hold false. Show that flag || true cannot tell "unset" from "set to false", then write the replacement that fills in only nil (flag.nil? ? default : flag) and print both versions for flag = nil, flag = false and flag = true.
Solution to 1

nil_false_and_truthiness_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Kata: blank?(x) -- the idea Rails calls "blank": nil, false, an empty or
# whitespace-only string, an empty array or hash. Plain truthiness says only nil
# and false are falsy, so blank? has to ask each object what it is.

def blank?(x)
  case x
  when nil, false then true
  when String then x.strip.empty?
  else x.respond_to?(:empty?) ? x.empty? : false
  end
end

puts "   value    truthy?  blank?"
[nil, false, 0, "", "  ", [], {}, "a", [nil], true].each do |x|
  puts "   #{x.inspect.ljust(8)} #{(!!x).to_s.ljust(8)} #{blank?(x)}"
end

Verified output of nil_false_and_truthiness_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

   value    truthy?  blank?
   nil      false    true
   false    false    true
   0        true     false
   ""       true     true
   "  "     true     true
   []       true     true
   {}       true     true
   "a"      true     false
   [nil]    true     false
   true     true     false

See also