Booleans don't exist in Ruby

Mike Burns

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 is 0 if the value compares equal to 0; otherwise, the result is 1.

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.