There’s a way to handle all of these, which preserves the property which we don’t currently have: for all if (a) A else B;
, we can also do if (!a) B else A;
with the same meaning.
Fundamentally, it’s a requirement that an if
on a !?T
have two else
branches, to handle all three cases. That would be a breaking change, but a detectable one which would have only local consequences and be easy to fix. The problem is that there would be, potentially, a lot of these to fix. I don’t know how much !?T
appears in real code, but it’s a distinctive pattern which would be easy to search for, if someone wanted to try answering that question.
Errors are checked first, then optionals, then the value. So let’s say that the return signature of the function f()
is !?T
…
if (f()) |t_val| {val_case} else {nil_case} else |err| {err_case};
if (!f()) |err| {err_case} else {nil_case} else |t_val| {val_case} ;
This would also change the semantics for ordinary if statements (from the documentation):
// Right now it works like this
const a: anyerror!?u32 = 0;
if (a) |optional_value| {
try expect(optional_value.? == 0);
} else |err| {
_ = err;
unreachable;
}
// But it could work like this:
if (a) |value| { // note missing .? below
try expect(value == 0);
} else {
// here, we handle the null case
// proposal for how to signal "we do nothing here" :
_ = _;
} else |err| {
_ = err;
unreachable;
}
I would prefer this. A !?T
has three return branches, so it should be an if
and two else
, in that order, mandatory. Using !
on the predicate reverses the order of those three branches.
For just ?T
and !T
types it’s simpler:
if (!might_work()) |err| {err_case} else |t_val| {val_case};
if (!optional()) {nil_case} else |t_val| {val_case};
Unlike the idea of unique keywords for the three faces of if
, I think this would be a good semantics for the language to have. It would be a pretty disruptive change, probably worth rejecting on that basis. But it does provide consistency for all permutations of boolean/optional/error.
You’d need to provide an inner if
/else
to handle !?bool
, ?bool
, !bool
, as you do now. To do otherwise would be overly complex. My first draft had if (!!f())
and even if (!!!f())
and it was completely insane.
But I would venture the opinion that “if
on a !?T
must have all three branches” is in keeping with the spirit of the language. It’s much like exhaustive switching on enums: handle all your cases.