Ruby has the literals true
and false
, which are the only constructors for
TrueClass
and FalseClass
, respectively. It’s common to think of those as
Booleans. It can also make sense to think of them as bits, depending on the
need.
But to quote Stafford Beer, the purpose of a system is what it does. What
does TrueClass
and FalseClass
do?
Conditionals
At a high level, if
works on “truthy” values: the “then” clause is evaluated
unless the conditional is false
or nil
. The inverse is true for unless
.
if 0
puts "this is run"
end
if ""
puts "this is also run"
end
if []
puts "you guessed it: run this line"
end
if nil
puts "never run"
end
if false
puts "also never run"
end
This is encoded in the RB_TEST
function in Ruby itself:
static inline bool
RB_TEST(VALUE obj)
{
return obj & ~RUBY_Qnil;
}
So we have our first definition: a Boolean in Ruby is one of two things:
false
or nil
, or anything else.
What the heck is that obj & ~RUBY_Qnil
thing in C? Is that …
Object Identifiers
Every object in Ruby has a number assigned to it. The interpreter or virtual
machine sometimes uses that number for optimizations. You can see this number
with the #object_id
method.
All instances of Integer
– that is, actual numbers – have an odd object ID,
and all other constants have an even one.
irb(main):001:0> 1.object_id
=> 3
irb(main):002:0> 2.object_id
=> 5
irb(main):003:0> true.object_id
=> 20
irb(main):004:0> false.object_id
=> 0
irb(main):005:0> nil.object_id
=> 8
So we can see that true values are 20 and false values are 0. nil
, which if
thinks is just as untrue as false
, is 8.
irb(main):001:0> true.object_id.to_s(2)
=> "10100"
irb(main):002:0> false.object_id.to_s(2)
=> "0"
irb(main):003:0> nil.object_id.to_s(2)
=> "1000"
So for example:
irb(main):001:0> [].object_id & ~(nil.object_id)
=> 192
irb(main):002:0> nil.object_id & ~(nil.object_id)
=> 0
irb(main):003:0> false.object_id & ~(nil.object_id)
=> 0
irb(main):004:0> 1.object_id & ~(nil.object_id)
=> 3
irb(main):005:0> true.object_id & ~(nil.object_id)
=> 20
OK but the C return type is bool
…
That’s right, RB_TEST
returns a bool
, but the &
operator produces numbers
like 192, 0, 3, 20, etc.
In C, Booleans are numbers: 0
is false and anything else is true. This is
represented, in C99, in the _Bool
type (aliased to bool
, as seen in
Ruby’s C code):
When any scalar value is converted to
_Bool
, the result is0
if the value compares equal to0
; otherwise, the result is1
.
That’s right: Ruby’s if
and unless
are truthy because C is truthy.
(You can also see this in the RBOOL
macro, in case you’re curious about
Ruby’s C implementation. RBOOL
will pop up again later!)
Interfaces
OK sure, but that’s some C. We’re here to talk about Ruby, the duck-typed
programming language we love. What interface do TrueClass
and FalseClass
share?
irb(main):001:0> true.methods - false.methods
=> []
irb(main):002:0> true.methods - Object.methods
=> [:^, :&, :|]
They share the #^
, #&
, and #|
methods, which look like little faces when
they’re symbols. Now we’re getting somewhere!
A Boolean in Ruby is anything that implements #^
, #&
, and #|
. Case closed.
irb(main):003:0> nil.methods - Object.methods
=> [:to_h, :rationalize, :&, :to_f, :to_i, :to_a, :to_r, :to_c, :|, :^]
Oh hello, nil
.
Standard library
Ruby methods that end in ?
are typically predicate methods – they are
expected to be used in conditional context such as if
and unless
. How does
the Ruby standard library define these methods?
I ran a quick Ripgrep over the Ruby stdlib:
~/ruby% rg -A5 'def .*\?' lib/
and skimmed through the 4600 lines of results. That’s 713 method definitions,
but -A5
shows the five lines after so I can see what they return.
And what I learned is that most of them are defined in terms of &&
, ==
,
||
, #any?
, #all?
, or other methods. We have to go back to C.
Some examples are Proc#lambda?
:
VALUE
rb_proc_lambda_p(VALUE procval)
{
rb_proc_t *proc;
GetProcPtr(procval, proc);
return RBOOL(proc->is_lambda);
}
and Complex#eql?
:
static VALUE
nucomp_eql_p(VALUE self, VALUE other)
{
if (RB_TYPE_P(other, T_COMPLEX)) {
get_dat2(self, other);
return RBOOL((CLASS_OF(adat->real) == CLASS_OF(bdat->real)) &&
(CLASS_OF(adat->imag) == CLASS_OF(bdat->imag)) &&
f_eqeq_p(self, other));
}
return Qfalse;
}
and Symbol#casecmp?
:
static VALUE
sym_casecmp_p(VALUE sym, VALUE other)
{
if (!SYMBOL_P(other)) {
return Qnil;
}
return str_casecmp_p(rb_sym2str(sym), rb_sym2str(other));
}
We see our friend RBOOL
(0
is false, anything else is true), an explicit
Qfalse
, and even a Qnil
(str_casecmp_p
has additional return values of
Qnil
).
From this we can learn that false
and nil
are falsy and anything else is
truthy. But is that really … true?
Third party libraries
Some major players are involved in the Boolean game. YARD needs to care about return types and argument types. Sorbet and RBS are static analysis tools. And Rails maps Ruby values to the database and back. What do they think?
YARD, Sorbet, and RBS each introduce the name “Boolean”. YARD defines it as
true or false
; Sorbet defines Types::Boolean
as OrType(trueClass,
falseClass)
. RBS offers Bool
as a “base type”, like class, nil,
void, top, and bottom; this Bool
only matches on TrueClass
or
FalseClass
.
Rails also introduces Booleans, in order to convert from HTML user agents or
databases into Ruby. The ActiveModel::Type::Boolean
class, for example, can
produce three different values: nil
, if the initial value is the empty
string; false
, if the initial value is false
, "0"
, "f"
, "false"
, or
"off"
; and true
otherwise. Similar fun exists for the ActiveRecord
connection adapters, where a MySQL tinyint(1)
gets parsed into either true
or false
.
This is the first time we encounter a traditional Boolean in Ruby, an
enumerable that can be exactly true
or false
. Rails, RBS, and Sorbet act as
a sort of translation project, mapping the expectations of the Language Server
Protocol, database, or user agent onto Ruby. All four of them attempt to map
how we are trained to think about software onto Ruby. You think there’s a
Boolean? Well we can pretend!
Booleans exist in the mind
And perhaps we get to the actual answer: Ruby has Booleans as much as you believe they do.
Early Ruby programmers often came from Perl, where we have five things that are treated as false (but, notably, no actual representation of a false or true value itself). We were told that truthiness was more of a feeling and – speaking for myself – I found it constraining to imagine a world with fewer than five false values. Empty list isn’t false?!
Further along we see Ruby devs playing with Haskell and, later, Rust. To make
their static analysis rigorous those languages support exactly two values in
their conditionals. This is when Rubyists starting adding !!
before return
values to force the value to be exactly one of true
or false
.
The concept of a Boolean is taught in computer science classes and programming bootcamps. They exist as common terms shared between programmers; of course we’re going to express them in Ruby. But to what extent Ruby itself has Booleans ebbs and flows, which truly is the beauty of Ruby itself. It lets us express Boolean ideas when we want them, and steps out of the way when we aren’t interested.
Does Ruby have Booleans? That’s a question for you and Ruby to decide together.