www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Should D have control-flow expressions? IS it possible?

reply Meta <jared771 gmail.com> writes:
While working on the POC for my discriminated union proposal 
(https://github.com/dlang/dmd/pull/23744), which includes the 
addition of switch expressions, I realized that I would really 
like to allow break/continue/return - even goto - in expression 
position as the result of a match arm. Imagine you're reading 
data from a network stream in a loop and want to break out if you 
get an end of stream packet, or want just continue on and not do 
anything for a heartbeat packet?

```d
// Tagged union over wire packets: unboxed,  safe, and  nogc
enum union Packet
{
     case Data(const(ubyte)[] payload),
     case Ping(ulong timestamp),
     case Reset(ushort errorCode),
     case Heartbeat(),
     case EndOfStream(),
}

struct NetworkStream
{
     bool hasData();
     Packet readNext();
}

size_t handlePayload(const(ubyte)[] data);
size_t sendPong(ulong ts);
void finalizeSession(size_t total);

void processConnection(ref NetworkStream stream)
{
     size_t totalBytesProcessed = 0;
     scope(exit) finalizeSession(totalBytesProcess);

     // Systems loop processing incoming stream frames
     while (stream.hasData())
     {
         auto pkt = stream.readNext();

         // Switch expression with value-producing and diverging 
arms
         size_t bytesHandled = switch (pkt)
         {
             case Data(slice)    => handlePayload(slice),
             case Ping(ts)       => sendPong(ts),
             case Heartbeat()    => continue,           // Nothing 
to do for heartbeat packets
             case EndOfStream()  => break,              // 
EndOfStream stops reading and closes the connection
             case Reset(err)     => assert(0, "Reset"), // Already 
works in D today
         };

         totalBytesProcessed += bytesHandled;
     }
}
```

After some time with an LLM trying to figure out how it could be 
done, I've discovered that it IS possible when a switch 
expression is used in statement position like this - the compiler 
recognizes the specific `case ... => break,` pattern, and that 
arm gets transformed into a BreakExp of type noreturn. During 
semantic, if the compiler detects that the switch contains a 
break expression, it lowers the whole thing into a series of 
if-else blocks, with the EndOfStream one containing a break 
statement, and it all Just Worksâ„¢ with no backend changes needed. 
I assume continue, return, and even goto would work pretty 
similarly.

Beyond switch expressions, there's no real reason why we can't 
have these control-flow statements embedded in *arbitrary* 
expressions:

```d
(true && break)
(cond()? return 42 : 10)
(1 + 1 == 2 || continue)
```

And so on.

But outside the hard-coded pattern outlined above that gets 
transformed by the compiler in a very specific case, it seems 
very difficult with the way the compiler is currently written. I 
don't completely understand all the technical details, but it 
seems that you can't just lower arbitrary expressions to 
statements.

This *does* work, but I assume it's a special case because 
exceptions unwind the stack. Hopefully I'm wrong:

```d
void main()
{
     for (auto i = 0; i < 10; i++) {
         (true && throw new Exception(""));
     }
}
```

Anyway, is this reasonably doable? I personally want it for 
switch expressions (or Rikki's proposed .match statement, which I 
think lowers to a tree of ternary expressions similarly to what 
my POC does).
Sep 09
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Thursday, 10 September 2026 at 06:56:25 UTC, Meta wrote:
 it lowers the whole thing into a series of if-else blocks... I 
 assume continue, return, and even goto would work pretty 
 similarly.
https://forum.dlang.org/post/xsjudliyxcfnhoxqymgg forum.dlang.org Nah, goto, continue, break, is a some kind of stack; if else chains could not explain this behavior. adr was looking into it recently maybe ask him but maybe youd have to extend whatever the hell the goto stack is to allow access with expressions
Sep 10
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Thursday, 10 September 2026 at 06:56:25 UTC, Meta wrote:
 [...]
 Anyway, is this reasonably doable? I personally want it for 
 switch expressions (or Rikki's proposed .match statement, which 
 I think lowers to a tree of ternary expressions similarly to 
 what my POC does).
The problem is about algorithmic complexity. D does the flow analysis at the statement level. That way you dont have to explore the whole sub-expressions to create the "basic blocks". You can allow expressions to change the flow, but this is a serious shift. For example Zig does that. In the past they had a lot of bugs related to the way they mix expressions and statements.
Sep 10
parent reply Meta <jared771 gmail.com> writes:
On Friday, 11 September 2026 at 03:35:47 UTC, Basile B. wrote:
 On Thursday, 10 September 2026 at 06:56:25 UTC, Meta wrote:
 [...]
 Anyway, is this reasonably doable? I personally want it for 
 switch expressions (or Rikki's proposed .match statement, 
 which I think lowers to a tree of ternary expressions 
 similarly to what my POC does).
The problem is about algorithmic complexity. D does the flow analysis at the statement level. That way you dont have to explore the whole sub-expressions to create the "basic blocks". You can allow expressions to change the flow, but this is a serious shift. For example Zig does that. In the past they had a lot of bugs related to the way they mix expressions and statements.
Hmmm, do you remember anything in particular? From what I've read, one of the big issues is around cleanup.
Sep 10
parent Basile B. <b2.temp gmx.com> writes:
On Friday, 11 September 2026 at 04:07:20 UTC, Meta wrote:
 On Friday, 11 September 2026 at 03:35:47 UTC, Basile B. wrote:
 On Thursday, 10 September 2026 at 06:56:25 UTC, Meta wrote:
 [...]
 Anyway, is this reasonably doable? I personally want it for 
 switch expressions (or Rikki's proposed .match statement, 
 which I think lowers to a tree of ternary expressions 
 similarly to what my POC does).
The problem is about algorithmic complexity. D does the flow analysis at the statement level. That way you dont have to explore the whole sub-expressions to create the "basic blocks". You can allow expressions to change the flow, but this is a serious shift. For example Zig does that. In the past they had a lot of bugs related to the way they mix expressions and statements.
Hmmm, do you remember anything in particular? From what I've read, one of the big issues is around cleanup.
for some reason I don't have access granteed to codeberg (running joke), but basically in the past, every bug related to "terminator" (when they used LLVM) [are about the flow analysis](https://github.com/ziglang/zig/issues?q=is%3Aissue%20state%3Aclosed%2 terminator%20LLVM). You see I have no problem with that prog-lang but due to my own interest I've spent a lot of time reading bug reports. As a user of LLVM I can tell you frankly that that used to be one of their main problem.
Sep 10
prev sibling next sibling parent ABrightLight <example example.com> writes:
On Thursday, 10 September 2026 at 06:56:25 UTC, Meta wrote:
 This *does* work, but I assume it's a special case because 
 exceptions unwind the stack. Hopefully I'm wrong:

 ```d
 void main()
 {
     for (auto i = 0; i < 10; i++) {
         (true && throw new Exception(""));
     }
 }
 ```
I think that this works because `throw new Exception("")` is inferred `noreturn`, which is implicitly convertible to all types. <https://dlang.org/spec/type.html#noreturn> As for some of the rest there, D does have labeled break and continue, but they cannot cross function scopes. I agree that there is room for more escape continuations (besides just exceptions and some limited labeling for gotos) than what is currently in the language, but not sure which could safely be added.
Sep 11
prev sibling parent reply DanielG <simpletangent gmail.com> writes:
(These are comments on your feature as a whole - let me know if 
there's a better thread for it. I don't see one in the DIP Ideas 
/ DIP Devel forums)

1. THIS is what sumtypes should look like!! I really hope you 
will create a competing DIP for this - I do NOT want 
type-unions-masquerading-as-sumtypes to be what we end up with.

2. IMO, drop the bare type cases. [It's 
cleaner.](https://i.imgur.com/nZ5q20o.jpeg) They don't really add 
anything, and bare type matching in `switch` should be the 
responsibility of whoever adds type unions to the language 
(versus vanilla sumtypes). Sumtypes and type unions should be 
orthogonal features, not mixed together.


original statement-based switch, and the newer expression-based 
switch (`value switch { ... }` instead of `switch(value) { ... 
}`). Does your feature already work in a normal switch? If so, I 
don't think the extra complexity (of control-flow expressions) is 
justified.

What I'm saying is, if somebody needs a break/continue, they can 
just use the normal statement-style switch. This would feel 
perfectly natural in D:

```d
	    // resort to statement-style switch when you need 
break/continue
         size_t bytesHandled;
         switch (pkt)
         {
             case Data(slice):
                 bytesHandled = handlePayload(slice);
                 break;
             case Ping(ts):
                 bytesHandled = sendPong(ts);
                 break;
             case Heartbeat():
                 continue;
             case EndOfStream():
                 break;
             case Reset(err):
                 assert(0, "Reset");
         };
```
Sep 11
next sibling parent DanielG <simpletangent gmail.com> writes:
(Whoops. My `EndOfStream()` break above should have been a 
labeled break or goto. But you get the idea)
Sep 11
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Saturday, 12 September 2026 at 05:27:01 UTC, DanielG wrote:
 1. THIS is what sumtypes should look like!! I really hope you 
 will create a competing DIP for this
It's your lucky day: https://forum.dlang.org/post/ppnebwqwqxgjdyaljmeq forum.dlang.org
Sep 13
parent DanielG <simpletangent gmail.com> writes:
On Monday, 14 September 2026 at 06:36:20 UTC, Meta wrote:
 It's your lucky day:
 https://forum.dlang.org/post/ppnebwqwqxgjdyaljmeq forum.dlang.org
😮 Indeed it is! I will comment in that thread later today after I wake up.
Sep 14