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
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









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