(splitting this off from #ideas > opting out of short-circuiting)
an idea I've thought about in the past, which would certainly make it easier to understand how these things work:
return for early returns? be syntax sugar for "early return if error" (like Rust does) and similar with !try keyword that wraps the expression right after it in a thunk that gets called right away, such that early returns inside the try end up returning to the try instead of to the enclosing functionI think I remember Rust planning to introduce something similar for their early return ? implementation
and there has been interest in early return independent of this topic
on the one hand, I do appreciate the simplicity of control flow that results from having no return, but on the other hand:
! and conditional statements, the short-circuiting rules have had a lot of situations where it's been hard to understand what they're doing, e.g. if foo then bar? else baz - even today, where do you expect that to short-circuit to? If ? desugars to return, the answer is immediately obvious in all situations with no more edges casesreturn, so Roc having it too would reclaim some weirdness budgetalso, by coincidence the last time someone directly asked for return in Roc (this was not on Zulip) was less than a week ago :big_smile:
Richard Feldman said:
an idea I've thought about in the past, which would certainly make it easier to understand how these things work:
here's specifically how that idea would desugar, assuming we have a return statement:
foo? bar
desugars to:
when foo bar is
Ok val -> val
Err err -> return Err err
and then this:
try fn arg
desugars to:
(\{} -> fn arg) {}
so if you replaced any of fn arg with expressions that used ? or ! or return, they would early return to the try because it wrapped them in a function
compared to our current desugaring rules for ? and ! this is astronomically more straightforward :sweat_smile:
which certainly appeals to me!
and I know this discussion started because of #ideas > Purity inference proposal v3 but I think the issues about the way short-circuiting works being nonobvious is a separate and reasonable thing to consider today, even setting aside that proposal
this example would work differently in this design; it would need a try to work:
Sky Rose said:
Would this work?
main : Str => U8 main = \path => result = str = File.readUtf8! path ?> FileReadError num = Str.fromU32? str ?> ParseError File.writeUtf8! path (Str.toU32 num) ?> FileWriteError when result is Ok _ -> 0 Err (FileReadError _) -> 1 Err (ParseError _) -> 2 Err (FileWriteError _) -> 3
so it would need to be this:
main : Str => U8
main = \path =>
result = try
str = File.readUtf8! path ?> FileReadError
num = Str.fromU32? str ?> ParseError
File.writeUtf8! path (Str.toU32 num) ?> FileWriteError
when result is
Ok _ -> 0
Err (FileReadError _) -> 1
Err (ParseError _) -> 2
Err (FileWriteError _) -> 3
https://doc.rust-lang.org/stable/unstable-book/language-features/try-blocks.html
This is the feature I think you're thinking of
I'm personally Rust biased, but having ? propagate to the function boundary for "someone else to handle" is really what I want most of the time. I'd rather that be the default than propagate to the def/expr level
Personally after using roc for a while, I really don't want early return
I think it strongly goes against some nice guarantees in roc
It is nice to know that things don't magically escape wrapping expressions.
You lose that with being able to stick a return in any random corner
Do you not like all early returns, or would you be okay with ? doing early return but not otherwise?
I do not think ? Should do it either
Having one expression tree without jumping around is super nice for understanding code and recognizing guarantees easily.
I know a deeply nested conditional statement will assign to the outer variables. It won't magically escape and return. That is really useful knowledge for debugging
You seem to be an "explicit over implicit" person, I presume you'd rather repeat yourself in having to use ? rather than it do it automatically, even in Rust?
(if you could change how Rust works)
Nope, but that is cause rust has a very different mental model from roc overall
Okay, imperative code vs expression trees
Roc is expressions all the way down. They are all self contained. It is great.
Rust is imperative and mutable and powerful in a different way
Until we add shadowing, you can still reorder all (pure) defs with ? in them and guarantee you'll get the same result
Not quite true for effectful code, but pretty close
I dont think this is about reordering
It's not
I'm getting at a concept of atomicity
I feel like I have grown to get a lot of comfort out of certain features in roc.
An expression tree with clearly contained logic is one piece. Another piece is explicit effects that are separated from pure logic.
Brendan Hansknecht said:
Having one expression tree without jumping around is super nice for understanding code and recognizing guarantees easily.
I know a deeply nested conditional statement will assign to the outer variables. It won't magically escape and return. That is really useful knowledge for debugging
I think we may have already lost this with our current ! and ? operators :sweat_smile:
Our current operators, yes
in #ideas > opting out of short-circuiting there's a discussion about whether we could have a rule set for ! and ? (just focusing on ? since it doesn't have any effects baggage) that restores this, and it's a big challenge haha
The other discussion in "opting out of short-circuiting" seems to be getting what Brendan and I think Agus want here
Richard Feldman said:
I think we may have already lost this with our current
!and?operators :sweat_smile:
Not really. Like yes, a minor bit, but only for a specific clear subset that is not deeply nested. If I see a nested expression under a variable I know what can or can't escape very easily
So I think this is quite different
If I see:
someVar =
# any number of infinite things
# if when, idc all fine
n + 1
It will never escape me in current roc. I know that someVar will be manifest and code after it will run.
So yes, but current ! and ? barely effect this
It's hard to predict all the consequences of this proposal
make
?be syntax sugar for "early return if error" (like Rust does) and similar with!
I do really value that what's behind the sugar would be a lot simpler
Anton said:
It's hard to predict all the consequences of this proposal
yeah I think this is maybe in the same bucket as effect polymorphism: don't introduce a new thing because there are enough new things already in this proposal, and we can always revisit later
If I may put my two cents as a complete beginner:
I find the ?> symbol more explicit as it relates to the pipe operator compared to the ?? that is used in the tutorial.
I think it's time to resolve this thread? The language changed a lot since then.
@Simon Taeter you would probably like to check #announcements channel to familiarize yourself with the changes in condensed format
Anton has marked this topic as resolved.
Last updated: Jun 16 2026 at 16:19 UTC