digitalmars.dip.development - First Draft: opUnwrapIfTrue
- Richard (Rikki) Andrew Cattermole (34/34) Feb 26 The DIP:
- Juraj (13/47) Feb 26 Feels like this will be hard to understand (I have to check
- Richard (Rikki) Andrew Cattermole (5/18) Feb 26 We talked a bit on Discord, and the point I've made is that this is a
- Kagamin (6/6) Mar 02 C# does in reverse with inline declarations
- Paul Backus (44/52) Feb 26 The "Rationale" section begins as follows:
- Dennis (20/28) Feb 26 So after this DIP, the value in the if-condition can now be
- Meta (7/36) Feb 26 Agreed; D already has limited pattern matching in template type
- monkyyy (6/11) Feb 26 So, one narrow opOverload, for one narrow type, for one narrow
- Per =?UTF-8?B?Tm9yZGzDtnc=?= (21/23) Mar 04 Nice.
- Richard (Rikki) Andrew Cattermole (5/34) Mar 04 I didn't add this particular bit of code, its already present and
- Ogion (71/74) Mar 12 We have a proposed tuple unpacking syntax[1] in this form:
- Richard (Rikki) Andrew Cattermole (7/48) Mar 12 DIP1053 did not provide this "TupleDeclarator".
- user1234 (9/15) Mar 20 I think that is not a good design.
- Walter Bright (34/34) Mar 13 Thank you for taking the time to develop this.
- Richard (Rikki) Andrew Cattermole (101/140) Mar 13 This pattern is called try-parse idiom or what I'll refer to as tryGet,
- Walter Bright (2/9) Mar 13 I don't know what is incorrect about it.
- Richard (Rikki) Andrew Cattermole (3/14) Mar 14 It is the wrong idiom for what is being archived, it is easily used wron...
- monkyyy (3/5) Mar 13 Its clearly the goal to avoid this and is not meta programming
- Jonathan M Davis (103/137) Mar 14 Well, ultimately, it's up to Walter and Atila to decide on this, but
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 } ```
Feb 26
On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole 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 } ```Feels like this will be hard to understand (I have to check `Result` for this op), I would much rather see movement on something like this: <https://github.com/dlang/dmd/issues/20645> Allowing this pattern: ```d if (int value; result.tryGetValue(value)) { ... } ``` IIRC, C++ has this feature as **if statements with initializer**
Feb 26
On 27/02/2026 4:28 AM, Juraj wrote:Feels like this will be hard to understand (I have to check `Result` for this op), I would much rather see movement on something like this: <https://github.com/dlang/dmd/issues/20645> Allowing this pattern: ```d if (int value; result.tryGetValue(value)) { ... } ``` IIRC, C++ has this feature as **if statements with initializer**We talked a bit on Discord, and the point I've made is that this is a different feature. Same with matching for sum types. They will coexist with this feature, like they do in Rust and Swift.
Feb 26
```
if (result.tryGetValue(out int value)) {
//use value
}
```
Mar 02
On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote:The DIP: https://gist.github.com/rikkimax/21242118e3bc1bf5f28024c2cdc33557The "Rationale" section begins as follows:In the authors codebase is a result type, with support for an error. It goes to an extreme extent to require a check for a given value of the result type to have been checked using `opCast!bool`, however this is very easy to miss when you do the `get` call which results in a runtime error. This was exhibited over many years period as being problematic.This seems like an entirely self-inflicted problem. You've designed an API that's a pain in the butt to use, and now you want to add a language feature to compensate for your bad design. A better solution would be to redesign your API to combine the check and the access into a single operation, either with a higher-order function like `Nullable`'s [`apply`][1], or with an `opApply` overload: ```d struct Result(T) { private T value; private bool hasValue; this(T value) { this.value = value; this.hasValue = true; } int opApply(Body)(scope Body body) { if (this.hasValue) return body(this.value); return 0; } } Result!int div(int n, int m) { if (m) return Result!int(n / m); else return Result!int.init; } void main() { import std.stdio; // prints 5 foreach (int result; div(10, 2)) writeln(result); // prints nothing foreach (int result; div(1, 0)) writeln(result); } ``` [1]: https://dlang.org/phobos/std_typecons.html#apply
Feb 26
On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki)
Andrew Cattermole wrote:
```D
if (int value = result) {
// got a value!
assert(value == 99);
} else {
// oh noes an error or default init state
}
```
So after this DIP, the value in the if-condition can now be
falsey.
```D
if (int value = result)
{
// value can be 0 here
assert(value); // can fail!
}
```
This looks very confusing to me. Note that Rust's `if let
Some(value) = result
` doesn't have this problem because the `Some()` makes it
explicit that unwrapping is going on. (Also int/i32 doesn't
implicitly convert to bool there).
You list range primitives as prior work, but notably they require
the dedicated foreach keyword instead of being an invisible
overload of the old `for` statement. I think if we add unwrapping
or pattern matching to D, it should have its own syntax.
Feb 26
On Thursday, 26 February 2026 at 18:03:31 UTC, Dennis wrote:On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote:Agreed; D already has limited pattern matching in template type lists and is-expressions anyway. If we're going to do something like this DIP, we should just instead add a new form of if statement or add pattern matching. There was a DIP years ago I think that proposed a very simple mechanism for adding custom unpacking functionality to types.```D if (int value = result) { // got a value! assert(value == 99); } else { // oh noes an error or default init state } ```So after this DIP, the value in the if-condition can now be falsey. ```D if (int value = result) { // value can be 0 here assert(value); // can fail! } ``` This looks very confusing to me. Note that Rust's `if let Some(value) = result ` doesn't have this problem because the `Some()` makes it explicit that unwrapping is going on. (Also int/i32 doesn't implicitly convert to bool there). You list range primitives as prior work, but notably they require the dedicated foreach keyword instead of being an invisible overload of the old `for` statement. I think if we add unwrapping or pattern matching to D, it should have its own syntax.
Feb 26
On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole 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.So, one narrow opOverload, for one narrow type, for one narrow usage pattern? Id suggest `opLeftAssign` that lets nullable!int define how int parses the assignment would be a more general feature
Feb 26
On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote:The DIP: https://gist.github.com/rikkimax/21242118e3bc1bf5f28024c2cdc33557Nice. Regarding the lowering ```d if (Result!int result2 = result, result2.opCast!bool) { scope(exit) result2.destroy; ... } else { result2.destroy; } ``` are ```d scope(exit) result2.destroy; result2.destroy; ``` eagerly elided when `__traits(needsDestruction, typeof(result2))` is false or are the injected the lower regardless and then later elided? Just checking to make sure the lowering doesn't inject AST unneccesary nodes.
Mar 04
On 04/03/2026 9:50 PM, Per Nordlöw wrote:On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote:I didn't add this particular bit of code, its already present and operational in dmd. I've tried to make the design and implementation very minimal, and I have succeeded at this! See PR. It does check if dtor is needed.The DIP: https://gist.github.com/ rikkimax/21242118e3bc1bf5f28024c2cdc33557Nice. Regarding the lowering ```d if (Result!int result2 = result, result2.opCast!bool) { scope(exit) result2.destroy; ... } else { result2.destroy; } ``` are ```d scope(exit) result2.destroy; result2.destroy; ``` eagerly elided when `__traits(needsDestruction, typeof(result2))` is false or are the injected the lower regardless and then later elided? Just checking to make sure the lowering doesn't inject AST unneccesary nodes.
Mar 04
On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote: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.We have a proposed tuple unpacking syntax[1] in this form: ```D Tuple!int result = tuple(99); (int value) = result; assert(value == 99); ``` We could use the same syntax for unwrapping optional types. This requires some additional rules: 1. If the type has a boolean value, it is checked before unpacking. If the check fails: 1.1. If the unpacking declaration is in `if` statement, execution takes the `else` branch (if exists). 1.2. If the unpacking declaration is in `foreach` statement, execution continues to the next iteration. 1.3. Otherwise, an Error is thrown. ```D mustuse struct Result(Type) { private { Type value; bool haveValue; } this(Type value) { this.value = value; this.haveValue = true; } bool opCast(T:bool)() => haveValue; (Type) = value; // TupleDeclarator (see DIP1053) } Result!int result = Result!int(99); (int x) = result; // throws an Error if `result` is false. if ((int y) = result) { assert(y == 99); } ``` Looping over Results: ```D foreach ((int x); getManyResults()) { writeln(x); } ``` It Just Works™: Result that holds a tuple: ```D Result!(Tuple!(int, string)) tupleResult = tuple(69, "nice"); if ((auto num, auto str) = tupleResult) { assert(num == 69); assert(str == "nice"); } ``` We could even apply the same syntax to pointers: ```D int[string] map = ["key": 69]; if ((int value) = "key" in map) { assert(value == 69); } int*[] arr = getManyPointers(); foreach ((int x); arr) { writeln(x); } /* Same as: foreach (p; arr) { if (p) { writeln(*p); } } */ ``` 1\. https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1053.md
Mar 12
On 13/03/2026 3:18 AM, Ogion wrote:On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote:DIP1053 did not provide this "TupleDeclarator". Right now that isn't a thing. I'm inclined to say that with ``opUnwrapIfTrue`` it could return a tuple, that then is supported in the condition. ``If`` statements in grammar have their own rules for var declarations so some more work is required for it to work.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.We have a proposed tuple unpacking syntax[1] in this form: ```D Tuple!int result = tuple(99); (int value) = result; assert(value == 99); ``` We could use the same syntax for unwrapping optional types. This requires some additional rules: 1. If the type has a boolean value, it is checked before unpacking. If the check fails: 1.1. If the unpacking declaration is in `if` statement, execution takes the `else` branch (if exists). 1.2. If the unpacking declaration is in `foreach` statement, execution continues to the next iteration. 1.3. Otherwise, an Error is thrown. ```D mustuse struct Result(Type) { private { Type value; bool haveValue; } this(Type value) { this.value = value; this.haveValue = true; } bool opCast(T:bool)() => haveValue; (Type) = value; // TupleDeclarator (see DIP1053)
Mar 12
On Thursday, 12 March 2026 at 14:18:00 UTC, Ogion wrote:On Thursday, 26 February 2026 at 13:53:41 UTC, Richard (Rikki) Andrew Cattermole wrote:I think that is not a good design. ``` assert(result); ``` Problems: 1. you are not sure the result is really checked ("-release" has for effect to disable assertions) 2. you dont create a branch for if the result is okayA 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.
Mar 20
Thank you for taking the time to develop this.
This is a bit simpler and doesn't require language changes:
```d
struct Result(T)
{
bool hasValue;
T value;
bool get(out T x)
{
if (hasValue)
{
x = value;
return true;
}
return false;
}
}
void bar(int);
void foo()
{
Result!int r;
int x;
if (r.get(x)) { bar(x); }
}
```
It also can be embedded in an expression and other constructs:
```d
int x;
switch (r.get(x) ? x : x.init)
{
...
}
```
To channel Andrei, destroy!
Mar 13
On 14/03/2026 8:14 AM, Walter Bright wrote:
Thank you for taking the time to develop this.
This is a bit simpler and doesn't require language changes:
```d
struct Result(T)
{
bool hasValue;
T value;
bool get(out T x)
{
if (hasValue)
{
x = value;
return true;
}
return false;
}
}
void bar(int);
void foo()
{
Result!int r;
int x;
if (r.get(x)) { bar(x); }
}
```
It also can be embedded in an expression and other constructs:
```d
int x;
switch (r.get(x) ? x : x.init)
{
...
}
```
To channel Andrei, destroy!
This pattern is called try-parse idiom or what I'll refer to as tryGet,
prior works section).
However unsurprisingly they also have language support for this stuff
based upon nullability but this is not unified unfortunately through
standard and usage:
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
There are two primary scenarios we need to consider for:
1. The value is required, either we have a default value, or we'll throw
an exception as its an error.
```d
struct Result(T) {
T get(lazy T default_);
}
void requiredInput(Result!int value) {
int unwrapped = value.get(throw new Exception("..."));
}
void defaultInput(Result!int value = Result!int.init) {
int unwrapped = value.get(0);
}
```
2. It is optional, or we want to do something different when not present.
```d
if (!result.empty) {
someObj.prop = result.front;
}
if (!result.empty) {
int unwrapped = result.front;
} else {
// do something
}
```
---
Th tryGet pattern only partially maps to the first scenario, and the
second is the exact problem motivating me to see us have a solution, a
get without the check.
```d
void requiredInput(Result!int value) {
if (!value)
throw new Exception("...");
int got = value.unwrap;
}
void defaultInput(Result!int value = Result!int.init) {
int got;
if (value)
got = value.unwrap;
}
```
For this version of ``requiredInput`` if you've got complex control
flow, that check may not have occurred, again a get without a check is
possible.
For the ``defaultInput`` if you forgot the check, after all nothing
requires it! You'd have been free to write:
```d
void defaultInput(Result!int value = Result!int.init) {
got = value.unwrap;
}
```
----
There are secondary use cases for opUnwrapIfTrue that I can see you have
not considered existing, here is the lowering for a ref variable:
```d
if (Result!int __temp = result, __temp.opCast!bool) {
scope(exit) __temp.destroy;
ref int value = __temp.opUnwrapIfTrue();
} else {
__temp.destroy;
}
```
The variable ``__temp`` is pinned, it cannot be modified by the user
directly, to do so you must go through the variable the user declared
``value``.
This controls lifetime, and allows for the declared variable to take
advantage of ref variables that you added not too long ago.
To get this to work you now need _another_ method, using callbacks.
```d
struct Result(T) {
Result ifTrue(void delegate(ref T) del) {
if (this)
del(this.value);
return this;
}
Result ifFalse(void delegate());
}
```
recommended in the context of DIP1000.
It is not a good solution, introducing multiple symbols with patching
just to workaround the lacking of a very simple transformation is not
the kind of idea that brings joy.
People complain about binary bloat, let's not increase it for something
so common by recommending a bad pattern. Note: this causes real problems
see October's quarterly meeting where Weka was maxing out a 31bit
integer for their patching of symbols.
---
Your switch example shows that you have not simplified the problem down
correctly.
``switch (r.get(x) ? x : x.init)``
Should be:
``switch (r.get(int.init))``
Mar 13
On 3/13/2026 6:45 PM, Richard (Rikki) Andrew Cattermole wrote:Your switch example shows that you have not simplified the problem down correctly. ``switch (r.get(x) ? x : x.init)`` Should be: ``switch (r.get(int.init))``I don't know what is incorrect about it.
Mar 13
On 14/03/2026 3:41 PM, Walter Bright wrote:On 3/13/2026 6:45 PM, Richard (Rikki) Andrew Cattermole wrote:It is the wrong idiom for what is being archived, it is easily used wrong. My reply was all about why this idiom is not one I can recommend.Your switch example shows that you have not simplified the problem down correctly. ``switch (r.get(x) ? x : x.init)`` Should be: ``switch (r.get(int.init))``I don't know what is incorrect about it.
Mar 14
On Friday, 13 March 2026 at 19:14:32 UTC, Walter Bright wrote:
int x;
if (r.get(x)) { bar(x); }
Its clearly the goal to avoid this and is not meta programming
viable; youd be expecting end users to correctly use `is`
Mar 13
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
Mar 14









"Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> 