First Draft: opUnwrapIfTrue

Richard (Rikki) Andrew Cattermole richard at cattermole.co.nz
Sat Mar 14 01:45:23 UTC 2026


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, 
C# uses it (I've since modified my local copy of DIP to include it in 
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());
}
```

This is an another existing idiom in C# and one Robert has in the past 
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))``



More information about the dip.development mailing list