`?`, I do not think it means what you think it means.

The ? character (Sometimes called the 'question mark' or 'interrogation point' among other names) is used many different ways in the context of computer programming. The purpose of this post is to look at four ways ? is used, and pontificate on what we like and what we don't.

? as the ternary operator

A ternary operator is basically just an operator that handles if/else branching more concisely. The ? plays this role for many programming languages, including C, C++, Java, and JavaScript. The basic idea is something like this:

condition ? exprIfTrue : exprIfFalse

Very often when programming, we want to express the idea "if some condition is true, evaluate this expression, otherwise evaluate that expression, and we want to do it in a single line. For example:

const bartenderSays = age >= 21 ? 'Comin right up' : 'I'm calling your parents';

The ternary ? lets us express this condition clearly and concisely. There are other approaches though. Take Python for instance.  The Python ternary looks a bit like an inline if/else statement

exprIfTrue if condition else exprIfFalse

Let's do our bartender example to make things more clear.

bartenderSays = 'Comin right up' if age >= 21 else 'I'm calling your parents'

I have to say, I think I like this approach better. Instead of having a second set of tokens for describing if/else conditions, Python just lets you put them in a different order. I think it's simpler and more readable.

Aside from Python, languages with pattern matching can generally do similar one liners to perform simple conditional logic. I think there's a solid argument to be made that using ? for a ternary is a waste of a perfectly good token.

? as error handling in Rust

In the Rust programming language, the ? is called the "try operator". I'm not going to try and teach you about traits and pattern matching and error handling in Rust here just to explain this operator to you. The purpose of the operator is very simple to understand. When used on a fallible operation, the try operator will perform the operation, and if the operation fails it will bubble up the error to the caller, otherwise it will keep going with the success value.

This is a pretty key part of how Rust achieves ergonomic error handling without having a separate control flow for errors (like Exceptions in Java). Any function that can error returns a Result<T, E> which contains either the result of a successful operation Ok(T) or an error of some kind Err(E). To take responsibility for an error, you can pattern match on the result of the function call. To pass the buck for handling the error to the caller, use ?. Here's an example:

let result = intial.do_a_thing()?.do_another_thing().do_the_last_thing()?;

Just by reading this line, we know that do_a_thing and do_the_last_thing can fail, and that if they do fail the error will be passed up to the caller. I personally really like this compared to an exception system like Java's where reading a list of method calls gives me no indication of which methods can fail. I'm pro question mark as error handling.

? as convention: booleans in Elixir

In the Elixir programming language, ? is not an operator at all. Instead, it's just another character like a e i o u. Because of this, Elixir allows identifiers (variable names, function names, etc.) to end in a ? character.

In Elixir, the convention is for variables and functions which evaluate to a boolean to end in a ? character. This is comparable to the convention in almost every other language of prefixing such identifiers with the word "is". So the Python

isActive = true

becomes

active? = true

Logically, the ? is the better choice. For starters, it's one character instead of two. It's also a much more universally understood symbol of a question than the English word "is", which matters for most programmers.

Maybe I need to broaden my horizons, but personally, I hate it. A ? FEELS like syntax, even if that's arbitrary. For other polyglot programmers, I suspect seeing ? in variable and function names elicits a similar response. It's just ugly.

? as null safety in Dart and C#

In the Dart programming language, a ? can be used to indicate that a variable is allowed to contain a null value. (C# allows for a similar syntax, except that some types are nullable by default, and therefore don't require a ?, whereas others do.) For example:

bool isAlive = true; // must have a value
bool? isAlive = null; // can be null

This syntax is extended with null aware operators like ??, ??= and ?. for dealing with nullable values. ?? and ??= are for providing defaults in case an expression evaluates to null. ?. is a short circuiting operator for handling null in method chains. They combine rather nicely. Here's an example from a stack overflow question.

bool outConn = outgoing[a]?.contains(b) ?? false;

If the result of outgoing[a] is null, the chain will short circuit as null, rather than calling a method on a null value. Then the ?? takes that null and swaps it for the provided default value, false.  

Personally, I really like this approach to null safety. It's disciplined and practical.

In Conclusion

I hope that this brief and incomplete survey of the ? character in programming was interesting, and if you disagree with my opinions, that's okay! See you next time.

Subscribe to BenIsOnTheInternet

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe