Do do_return !
- Document number:
- P4323R0
- Date:
2026-08-11 - Audience:
- EWG
- Project:
- ISO/IEC 14882 Programming Languages โ C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
- GitHub Issue:
- wg21.link/P4323/github
- Source:
- github.com/eisenwave/cpp-proposals/blob/master/src/do-do-return.cow
expression
to yield a result.
This paper argues against that feature for a variety of reasons.
Contents
Introduction
Problems with omitting do_return
Novelty and inconsistency
Competing styles
Visually grepping control flow
Accessibility problems
Textually grepping control flow, future direction
Irregular void and latent bugs
Conclusion
References
1. Introduction
[P2806R4] proposes a new feature of expressions,
allowing the user to put statements inside of an expression.
One notable design aspect is that expressions permit omitting the statement
to produce a result:
In its simplest form:
int x = do { do_return 42 ; } ; int y = do { 42 } ; // equivalent to above A
expression consists of a sequence of statements, but is still, itself, an expression (and thus has a value and a type).do
I argue that this is a bad idea,
and that should be required,
for the reasons listed below.
2. Problems with omitting do_return
2.1. Novelty and inconsistency
The ability to omit keywords like which would yield results from expressions
is a staple of other languages,
such as Kotlin and Rust.
The key observation is that in those other languages,
the last statement is result
design is consistently applied across the language,
for a wide variety of constructs.
This is not the case in [P2806R4],
making that design novel and surprising for expressions.
C++ users have been taught for 30 years that
- blocks enclosed by braces contain a bunch of statements,
-
you put a semicolon at the end of the line
(unless the line starts with
or whatever), andif -
control flow interactions
(without which you flow off the end of a block)
require a keyword like
,if ,return ,break , etc.co_yield
There is an immense amount of value in these consistent and teachable rules, which [P2806R4] proposes to weaken, for the purpose of reducing verbosity. These rules would no longer be universal rules.
Furthermore, why is the design not applied to other constructs, even though it would be possible in principle?
There are two possible directions here:
-
Not changing lambda expressions would be inconsistent
with
expressions, without good rationale. We would still sacrifice the consistent and teachable rules by makingdo expressions special, but we wouldn't get much value in return. Verbosity of lambdas is a common complaint.do -
Changing lambda expressions like this retroactively might be fine,
but it demonstrates that the
last statement is the esult
feature is orthogonal toexpressions, and that the design needs to be explored holistically.do
Either way, the conclusion seems that [P2806R4] should not be proposing this design. Perhaps it could be done in a separate paper for various constructs in general.
2.2. Competing styles
An inevitable outcome of letting users omit
is that there will be competing styles,
where some users prefer to always write .
This would be motivated by consistency with control flow in other places,
like and .
A C++ developer used to omitting would find it jarring to work on a code base
where is always required, and vice versa.
While having some disagreement over style is natural in a programming language,
C++ already has a ton of options without a clear winner
(initialization style, trailing return types, when to use , etc.),
and this issue would be compounded.
There is real cost to adding more and more competing styles
which then either need to be litigated by each code base
or lead to inconsistent code within one code base.
2.3. Visually grepping control flow
In the following code block, try to find all the places where control flow is altered in some way:
Despite this block being blurry and thus completely unreadable,
you can actually identify all the , , and statements
easily do to their shape and syntax highlighting.
The same visual identification
of control flow is done
subconsciously by developers all the time.
Searching for a statement in a long function doesn't require reading the entire
function from top-to-bottom, but rather scanning
for the shape and color of a statement.
As things stand, universally, builtin control flow constructs in C++
can be visually identified in this way.
This would not be the case for ,
which can be omitted.
2.4. Accessibility problems
In the proposed design, there is a major difference between the following expressions:
This design is hostile to users with poor eyesight
because a semicolon has major semantic impact.
It also shows that having competing styles is not just a matter of preference;
one user may prefer to omit because they have 20/20 vision
and they optimize for brevity,
which is detrimental to other users with poor eyesight.
The design also poorly integrates with screen readers
because it requires keeping the screen reader in high verbosity mode
to read code.
Historically, C++ semicolons could have been largely omitted as noise
because they were merely statement terminators,
with a few exceptions like .
could have been pronounced as
int x equals zero int y equals zero
without loss of information.
2.5. Textually grepping control flow, future direction
Another way to scan for control flow is to perform a text search.
If the user is confronted with a long expression and wants to find all the places
where it yields a result,
they can search for and jump from search result to search result.
This is not possible when the only distinguishing factor between
yielding a result and yielding is a semicolon.
Perhaps initially, the issue is not so bad because the user only has
to check the last statement of a expression to see if it yields a result.
However, a logical next step in language design is to allow the following:
This wouldn't be a breaking change because
yielding a result of the
requires omitting semicolons inside the expression statement,
which is currently not possible.
Once again, we are confronted with uncomfortable choices:
-
If we want to pursue omitting
in more places, we should commit to it fully, and consider the consequences for all possible statements rather than just the last statement in ado_return expression. There is a slippery slope here.do -
If we don't want to pursue this,
the current design of [P2806R4] is half-baked.
If Rust and Kotlin let you omit the
equivalent in more places, why can't we? Why would Rust be such an inspiration for the paper if we cherry-pick only a small portion of its syntax and consider the same Rust syntax in other construct to be a bad fit for C++?do_return
2.6. Irregular void and latent bugs
Another observation is that in Kotlin and Rust, the following construct works fine, when it doesn't in C++:
Consequently, the existence of the semicolon becomes hugely significant, especially in templates, where diagnostics are delayed until instantiation.
If the user forgot to append a semicolon to the last statement
but they intended the expression to be a expression,
their code would inadvertently return something unexpected,
and storing the result in a variable would compile even if it makes no sense.
By comparison, it is much less likely that the user forgets prepend
before an expression.
is obviously wrong when you're used to seeing
a big, syntax-highlighted at the end of your blocks.
If the user accidentally added a semicolon (perhaps out of habit or due to muscle memory),
their code would not compile because there is no regular ,
so the result couldn't be stored in .
Again, the fact that there is a stray semicolon
is much less obvious than a missing or .
Currently, this can be diagnosed prior to instantiation because the expression
must return based on its syntax
and is not a valid type for ,
but this could change in the future if was made more regular in the language.
3. Conclusion
The discussion above shows that there are many serious problems
with omitting in expressions.
Consistency with other constructs,
teachability,
stylistic consistency,
accessibility,
visual and textual greppability,
and other positives aspects of the language are affected
where expressions are used without .
Either, that direction should not be pursued at all, or it should be pursued holistically. That is:
-
Do we want to allow omitting the
statement in lambdas, and/or in functions, in general?return -
Do we want to allow omitting
in the lastdo_return and/orif statement of atry expression? What if we want to makedo and/orif an expression in the future?try - What other language constructs are affected by this, now and in the future?
- How do these decisions impact overall teachability, greppability, and accessibility of C++?
If we simply ignore these concerns,
we risk losing sight of the big picture.
This is unjustifiable, especially considering that the omission of
can be introduced at any point in the future without breaking changes,
since the omitted-semicolon syntax used by the feature is currently ill-formed.