And on one final note, allow me to share the state of things in C++:
C++ still does not have tagged unions but it does have an Option
type. Without tagged unions, one might ask, how do people work with them? Easy! By having a separate type that represents None
, and using overloading.
rust
let mut opt;
opt = None;
opt = Some(3);
let x = opt.unwrap_or(4);
c++
// The following "just works" because optional's operator= has overloads
// for arguments of nullopt_t and T.
std::optional<int> opt;
opt = nullopt;
opt = 3;
int x = opt ? *opt : 4;
Like rust, C++ has a heavy focus on using generic code for algorithms. So comparisons of Options have the same apparently broken semantics as Rust, to ensure that they form a total order.
rust
let a = Some(0);
let b = None;
assert!(b < a);
c++
std::optional<int> a = 0;
std::optional<int> b = nullopt;
if (!(b < a))
throw std::runtime_error("something's seriously wrong, yo");
Alright. Okay. Seems reasonable so far.
But the C++ committee didn't stop there. For reasons I can hardly even begin to imagine (though I'm sure they must have had a specific use case in mind, like generic lambdas or somesuch), they decided that the comparison operators also needed to work seamlessly in the same manner as assignment.
c++
std::optional<int> a = 0;
std::optional<int> b = nullopt;
if (!(nullopt < a && b < 0))
throw std::runtime_error("something's seriously wrong, yo");
That's right. Not only does C++ let you compare Options with comparison operators (with the same seemingly broken semantics as rust), but it also lets you compare an Option<T>
with a T
! With those very same semantics!!
This was, of course, the very first mistake I made when I recently gave C++17 a try.
...so if you ask me, I say we count our blessings!