Introduction
The Boolean type (spelled bool
in C, C++, Go, and Rust, among others; and boolean
in Java) is fundamental to programming since so many things are either true or false. Of course you can use bool
for function parameters, and it’s tempting to do so — but you probably shouldn’t. Consider a function call like:
void *buf = alloc_buf( 4096, true ); // What does "true" mean?
Seeing a literal true
or false
as part of a function call tells you nothing about what it means. Does it mean the buffer is maximally aligned? Zeroed? Prints an error message if the allocation fails? You have no idea. You have to find and read the either function’s declaration or documentation to know what it means:
void* alloc_buf( size_t size, bool zero_buf );
It’s for this reason I personally try to avoid using bool
for parameters in public APIs. Code is read far more than it’s written, so clarity matters. But what can you do instead of using bool
? That’s what this article is about.
This article’s examples are specifically in C but apply equally well to C-like languages or any language that has a Boolean type.
Alternatives to bool
There are a few alternatives depending on circumstances and taste.
Use Two Functions
Instead of a single function, have two:
void* alloc_raw( size_t size );
void* alloc_zero( size_t size );
Note that this would be in the public API. Internally, you can still implement a single function that takes a bool
if you want and have the public functions just call the internal function.
Use an Enumeration
Instead of using bool
, define an enumeration:
enum alloc_opts {
ALLOC_RAW,
ALLOC_ZEROED
};
typedef enum alloc_opts alloc_opts;
void* alloc_buf( size_t size, alloc_opts opt );
Of all the languages mentioned, only Go doesn’t directly support enumerations — but you can fake it.
You might think creating an enumeration to replace a bool
is overkill, but now look how the function is called:
void *buf = alloc_buf( 4096, ALLOC_ZERO );
It’s barely longer, but much clearer. Investing more in functions’ APIs has a many-fold return on said investment in terms of clarity.
Another benefit of using an enumeration is that it allows for additional options to be added easily by converting the enumeration to use bit flags:
enum alloc_opts {
ALLOC_RAW = 0,
ALLOC_ALIGNED = 1 << 0,
ALLOC_ZEROED = 1 << 1,
};
Then you can do things like:
void *buf = alloc_buf( 4096, ALLOC_ALIGNED | ALLOC_ZERO );
Note that code already using the function doesn’t have to change (unless you want to use the new options); it just has to be recompiled.
Use Comments (as a Last Resort)
If your code either calls a 3rd-party function that takes a bool
parameter or you simply can’t change your function’s API for whatever reason, at least use inline comments that repeat the name of the parameters:
void *buf = alloc_buf( 4096, /*zero_buf=*/true );
Programmers reading your code (including yourself in several months’ time) will thank you.
Conclusion
Since bool
is so easy, it’s very tempting to use it for function parameters, especially when retrofitting an existing function just to tweak it. Avoid the temptation: either add a second function or use an enumeration.
Top comments (3)
I always go for enums in situations like this. Especially with Rust's inline docs, being able to instantly see what the enum is for and what a certain variant does makes my life a lot easier.
I always try to avoid
enum
s and useuint8_t
& macros instead, asenum
s take up 2 or 4 bytes of memory (depending on the compiler and architecture), and often 1 byte is enough to store such data. But I'll likely switch to them everywhere except preprocessor programming.Unless you have special circumstances, using an extra few bytes for a parameter (that's passed on the stack or, more likely, in a register, i.e., temporary), it simply doesn't matter. But if you insist, starting in C23, you can specify the underlying type of an
enum
; same with C++:The other advantage of using an
enum
is that gdb and lldb are smart enough to print their names when debugging rather than their underlying integer values — even when using bit fields.