First Draft: opUnwrapIfTrue

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sun Mar 15 05:43:41 UTC 2026


On Thursday, February 26, 2026 6:53:41 AM Mountain Daylight Time Richard Andrew Cattermole (Rikki) via dip.development wrote:
> The DIP:
> https://gist.github.com/rikkimax/21242118e3bc1bf5f28024c2cdc33557
>
> A new operator overload to allow if statements to automatically
> unwrap result types and ensure that the check is called prior to,
> similar to foreach statements support of ranges.
>
> This solves a prolific issue that I have had to deal with result
> types and has been exhibited by ``Nullable`` in PhobosV2.
>
> Full example:
>
> ```d
> import core.attribute : mustuse;
>
> @mustuse
> struct Result(Type) {
>      private {
>          Type value;
>          bool haveValue;
>      }
>
>      this(Type value) {
>          this.value = value;
>          this.haveValue = true;
>      }
>
>      bool opCast(T:bool)() => haveValue;
>
>      Type opUnwrapIfTrue() {
>          assert(haveValue);
>          return value;
>      }
> }
>
> Result!int result = Result!int(99);
>
> if (int value = result) {
>      // got a value!
>      assert(value == 99);
> } else {
>      // oh noes an error or default init state
> }
> ```
>

Well, ultimately, it's up to Walter and Atila to decide on this, but
personally, I don't like the fact that it means that you have to look up the
type of result to know what's going on here. Even worse, if you have
something like

    if(auto value = foo())
    {...}

then what the code does is dependent on what foo returns, which is even less
obvious, since there won't be a variable declaration within the function to
at least know what the type being used is. You'd have to either be familiar
with foo already or look it up - though with how often auto is used in D
code, even with result, you'd probably have to look up the function that
generated it to know what it's type is, since it was probably declared with
something like

    auto result = foo();

So, in order to know the behavior of the if statement conditional, you're
generally going to go look up definitions in other code.

This is a case where the operator overload completely changes the semantics
of what's going on, whereas generally speaking, operator overloads are there
to add abilities to user-defined types which are in line with what those
same operators do for built-in types. So, IMHO, the proposed an operator
would reduce code clarity, and I think that if we add a feature like this
that it should have explicit syntax which is distinct from a simple variable
declaration so that it's clear that it's not the case that the variable is
being checked for truthiness as would normally be the case.

I confess I also don't see much utility in this, since I see no problem
whatosever with code such as

    if(result.hasValue)
    {
        auto value = result.get;
    }

And if anything, I'd probably just use result.get everywhere within the
branch without bothering to declare a variable to copy it into. And if I
were going to have syntatic sugar, I'd want the type to use pointer syntax
so that it would be something like

    if(result)
    {
        auto value = *result;
    }

which you can do right now (and I'd also probably just use *result
everywhere in that case rather than copying it).

The main reason behind the proposal seems to be that separating the hasValue
and get calls runs the risk of get being called when hasValue is false,
which is then a source of bugs, but personally, I don't recall the last time
that I ever encountered a bug like that. It's not something that I would
have considered error-prone. So, for myself at least, it seems like it's
trying to solve a problem that isn't really a problem, though I can
certainly believe that other folks code in a way that hits this issue more
frequently (just like I really don't understand why so many folks get bent
out of shape about dereferencing null pointers, since in my experience,
that's a very rare problem and easily caught and fixed). So, it seems like a
non-issue to me, but to be fair, there are probably a number of mistakes
that I routinely make that plenty of other folks would think are non-issues.

Either way, even if the separation of hasValue and get is a big enough
problem to merit a language feature, I would like it to be something with
clear syntax, not something that's invisible and requires knowing what type
result is in code such as

    if(auto value = result) {}

or what type foo returns in code such as

    if(auto value = foo()) {}

in order to have any idea whether value is being checked for truthiness or
whether the object that value is being taken from is being checked for
truthiness. IMHO, the ambiguity is a problem, and the feature should have
unambiguous syntax which does not look like existing code.

And as others have pointed out, it's particularly bad when you consider
cases such as

    if(int value = foo()) {}

since right now, you can assume that value is non-zero within the if
statement, whereas with the proposed operator, that would not necessarily be
the case - which would be fine if the syntax were distinct, but it's not.

Another issue would be that it would mean that generic code can no longer
rely on

    if(auto value = foo()) {}

checking whether cast(bool)value is true or not. I'm not sure that such
checks are particularly common in generic code, since plenty of types cannot
be cast to bool, but any generic code which works with types which can be
cast to bool would then do something different with any types which
implement the proposed operator. So, instead of implementing an overloaded
operator making it so that a type can work with more code, it makes it so
that code potentilaly has to check for this operator to get the correct
behavior, or it will have to avoid declaring variables in if conditions in
order to not trigger it.

None of that would strictly speaking break existing code, since the operator
would have to be added to an existing type to change its behavior, but it
would mean that such types would potentially not work correctly with
existing generic code, forcing such code to add additional template
constraints and/or static if branches to work correctly with such types. And
in the future if a struct which currently overloaded the cast operator to
work with bool then had this new operator added to it, that would almost
certainly break existing code, because any if conditions where the type
would be used would suddenly change behavior (and might or might not result
in compilation errors when the resulting variable was used).

So, personally, I'm against the DIP. I think that unambiguous syntax is
needed - syntax where when you look at it, you know that a wrapper type is
being checked for truthiness and not the variable being declared.

Also, Paul Backus has argued in discord that a generalized pattern matching
solution would be better on the grounds that it should be able to solve this
problem while providing more general value rather than just solving a fairly
narrow problem, but I'm not familiar enough with what that would look like
to really argue that one way or the other. So, I think that it's probably a
good idea to consider that, but that's obviously a _much_ more involved
discussion.

- Jonathan M Davis






More information about the dip.development mailing list