Skip to content

Conversation

WebEferen
Copy link

This proposal introduces do...while loops to Carbon, providing a post-test loop construct that executes the loop body at least once before checking the condition.

Key features:

  • Syntax: do { statements } while (condition);

  • Executes loop body at least once, then evaluates condition

  • Supports break and continue statements

  • Addresses common patterns like input validation, menu systems, and retry mechanisms
    Benefits:

  • Eliminates need for code duplication or while(true) workarounds

  • Improves code readability for post-test loop patterns

  • Maintains consistency with C-family language conventions

  • Enhances C++ migration compatibility

This complements Carbon's existing while and for loop constructs, providing developers with a complete set of loop control structures.

@github-actions github-actions bot added the proposal A proposal label Jul 8, 2025
@github-actions github-actions bot requested a review from zygoloid July 8, 2025 11:19
@jonmeow
Copy link
Contributor

jonmeow commented Jul 8, 2025

FWIW for history on this, proposal #340 added while loops, and notes that do/while was discussed but undecided at the time. To offer a little more detail, my recollection is that leads were interested in a more flexible loop syntax to achieve the same job, and also support other use-cases: in particular, arbitrary logic execution on continue and break. A do/while is a particular form of logic execution on continue.

Note this is also mentioned in #353, for why for (...; ...; ...) is not included: again, the intent was towards a syntax that could address both use-cases, and more.

Copy link
Contributor

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll definitely need to consider whether this is a direction we want to pursue. Some early feedback:

The concrete syntax do ... while (...) has historically been problematic in C-family languages. It adds significant context-sensitivity to the while (...) construct, making it mean something different if there's a preceding do .... Eg, if you see

  ...
}
while (cond);

in C++, it's unclear locally whether the while attaches to a prior do or not. This wouldn't be so bad in Carbon because braces are mandatory on nested statements, so we could distinguish the two based on whether the while is followed by a ; or a {, but the reuse of the while keyword and following syntax is still somewhat a concern.

More broadly, though, we should consider whether we want to address the "loop-and-a-half" use case as well as the "at least once" use case, plus any other similar loop modalities. (The loop-and-a-half case is something like:

vector<expr> exprs;
while (true) {
  exprs.push_back(parse_expr);
  if (next_token() != comma) break;
  consume_token();
}

... where the loop condition isn't at the start or end of the loop but somewhere in the middle.) It'd be good to have some data on how common do...while is compared to other loop modalities -- that'd inform whether it's worth having dedicated language syntax for this, including a new (2 letter) keyword, or whether we should pursue something broader, or just tell people who want do...while to instead use

while (true) {
  ...
  if (!cond) break;
}

@jonmeow
Copy link
Contributor

jonmeow commented Jul 9, 2025

@zygoloid Regarding putting an if at the end, I think it would be more challenging to transform mid-loop continues, like:

do {
  ...
  if (cond1) continue;
  ...
} while (cond2);

With if, it can lead to repetition of the condition (or errors if not repeated), for example:

while (true) {
  ...
  if (cond1) {
    if (!cond2) break;
    continue;
  }
  ...
  if (!cond2) break;
}

e.g., something we'd discussed in the past was:

loop {
  ...
  if (cond1) continue;
  ...
} on continue {
  if (!cond2) break;
}

Note that's also flexible in terms of "on break", etc.

@dwblaikie
Copy link
Contributor

So I guess the generalization of these is:
a) Once at the start (traditional for init)
b) Before the condition (even the first time) (do ... while body)
c) The condition (traditional for condition)
d) After the condition (traditional for body)
e) Before going back to the condition (traditional for increment)

And the difference between this superset and something like C++'s for (x; y; z) body; is that at least some (more) of these ^ would want a whole block, not just a single statement, but that having to write out empty blocks for all these parts would be overly verbose, introducing keyword names for all of them seems awkward too...

(& mid-loop conditions makes me think of Duff's Device... )

It's like we need the opposite of an else - an optional /prefix/ to a loop?
do ... while() equivalent:

prefix {
  b;
} while (c) {
}

mid-condition:

prefix {
  b;
} while (c) {
  d;
}

Mid-condition for:

prefix {
  b;
} for (c: C in blah) {
  d;
}

The words aren't great - while C-family developers are already comfortable with the fact that the increment in a for (;;) happens somewhat removed from where it's written (notionally at the "end" of the body of the loop) - I think if we were to move the condition to the top of the loop (prefix in the above examples) but evaluate it halfway down the loop, that'd be a struggle. Alternatively, having the looping construct keyword in the middle is going to be a struggle to realize the code above it is part of the loop...

Should the prefix be thought of more as a tail instead? Is that equivalent?

for (c: C in blah) {
  d;
} start {
  b;
}

do-while would be:

while (c) {
} start {
  b;
}
Lifetimes would be a bit less intuitive (obvious enough that `b` can't see `c`, due to the `{}`, but should the increment operation be evaluated before or after `b`?)... 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal A proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants