Relevant issue: https://github.com/roc-lang/roc/issues/7088
@Sam Mohr said:
To promote Roc developers providing context for Results when propagating errors with
try, we want to add a new operator ? that acts as aResult.mapErrequivalent. Some example usages:```roc
fileContents =
try File.read! "file.txt" ? FailedToReadFilefileContents =
try File.read! "file.txt" ? FailedToReadFile{} = File.save! "file.txt" contents ? \err =>
Logging.error! "Failed to save file.txt: $(Inspect.toStr err)" FailedToSaveFile err```
It should be higher precedence than
trysuch thattrywill propagate errors after ? is applied, but it does not need to be used withtry.We should always attempt to format onto the same line as the fallible code, but can optionally wrap to a newline with an indentation to imply continuation; this prevents code from reading like we apply ? after
try.The new ? operator should be able to handle mapping over errors using pure or effectful mappers, meaning it should be its own AST node. We can start with a pure-only implementation, since we are waiting on purity inference still.
If a pure handler is used with the new ?? "Result.withDefault" operator, we should raise a warning that the error mapping is unnecessary, since it will always be discarded by ??.
Does this bind tighter than try? and how does this feel when we move to static dispatch with ? postfix operator?
fileContents =
File.read!("file.txt")? ? FailedToReadFile
And Richard in a Zulip thread suggested that this should always be early return on error like try. Basically, append a mapErr with the provided Err tag and then desugar the try.
Maybe a keyword is more appropriate? The same could be said for ?? I think.
Maybe orelse for ?? and errwith(or orthrow) for ?
I think
fileContents =
try File.read!("file.txt") errwith FailedToWrite
or
fileContents =
File.read!("file.txt")? errwith FailedToWrite
Read better than the above. But then with Static Dispatch do we gain anything when it would just be
fileContents =
File.read!("file.txt").map_err(FailedToWrite)?
I think Richard's suggestion was that the spaced ? would be sufficient without the suffix ?, AKA
fileContents =
File.read!("file.txt") ? FailedToReadFile
Which might be a little confusing, but it still follows the "? means propagate errors"
But now "right after ) means just propagate the error, space between ) and ? means map the error first"
But if we did want to go for operation(arg)? ? myErrorMapper, then the second ? should be something else, and a keyword like errwith would be a good way to do it
I was confused by this line from the issue:
Relevant issue: https://github.com/roc-lang/roc/issues/7088
@Sam Mohr said:
It should be higher precedence than
trysuch thattrywill propagate errors after ? is applied, but it does not need to be used withtry.
That original description is out of date
This was before PNC was even suggested, so we thought ? wouldn't exist outside of this and try would be the only error propagator
Now that ? is definitely the long-term plan for error propagation, we should design around that
In the #ideas > static dispatch - homepage example thread, Richard said:
so
foo() ? barwould desugar towhen foo() is Ok val -> val Err err -> return bar(err)
And that's what I'm referring to as the modern plan for this
Awesome
Thanks for the clarification
Yeah, no biggie, but that's why we prefer people come in here and validate that issues are up to date before picking them up
Because it's "tornado season" most of the year in Roc world
And stuff gets outdated often
Yep
I wonder how we could address this. Both in issues and in the tutorial
I think we should have a CI job that tests all tutorial code samples
And force people to update when they break with new syntax/semantics
The tutorial for sure, but examples in issues that are for future syntax will probably make testing that hard
If we write the tutorial with markdown, then we can add testing Roc blocks in markdown to achieve that effect
I think the pace of changes that will affect the tutorial is going to slow down a lot after 0.1
but definitely more things will change between now and then, so I think probably the best time to make an investment in tutorial infrastructure is after we land the 0.1 changes :big_smile:
For sure, but if it's possible to validate the tutorial's syntax in CI, that would be nice
The tutorial is already written in markdown :)
Oh yeah, good point! I was thinking about what I expect the future tutorial to be written as, which I expect would be a multi-page markdown tutorial, but it could be written in something else to facilitate being an interactive tool, like HTML or even Roc. It'd be good to preserve markdown for the above reasons
roc test can handle markdown files too. The last puzzle piece we need to implement is some type of (comment) syntax that allows us to hide imports for display but still use them for testing.
I was thinking of that. We want to allow single hash comments within doc examples, but we can probably use a second layer of doc comments for hidden code?
Yeah, that could work
That's exactly what Rust does: https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#hiding-portions-of-the-example
Richard Feldman said:
so
foo() ? barwould desugar towhen foo() is Ok val -> val Err err -> return bar(err)
Is this definitely our chosen direction?
Any chance we could sneak this in with the current batch of changes too?
Would it be return Err(bar(err)) or return bar(err)
the former
I guess that latter enables doing fallible operations
it's supposed to be like map_err
Can you do
foo() ? |ReadErr(data)|
Stderr.line!("Failed to read file: ${data.to_str()}")?
Err(my_err)
I think not
But yes, it's like map_err
So the former
the main use case is ? CustomErrorTag or ? |_| CustomErrorTag
Yep
Just realizing that it is less powerful than I thought
But that's okay
Ok got it.
when foo() is
Ok val -> val
Err err -> return Err(bar(err))
I say we go for it
Richard Feldman said:
the main use case is
? CustomErrorTagor? |_| CustomErrorTag
Great, I can see lots of places we should use this in the examples
It's literally perfect for scripting
Just throw a Cmd.exec!("mv", [old, new]) ? FailedToSaveConfig on everything you want tracing on
It's the anyhow of Roc
Just to verify, are we saying, we only get Cmd.exec!("mv", [old, new]) ? FailedToSaveConfig and not Cmd.exec!("mv", [old, new])?? Or is it both?
both
awesome!
Does Cmd.exec!("mv", [old, new])? ? FailedToSaveConfig early return before or after mapping the error?
You shouldn't use both
But if you did, it'd early return first (I'm pretty sure)
Great, so it’s a syntax error?
It'd be a type error
Superb! Thanks Sam!
yeah the precedence would be that the suffix ? happens first and then afterwards the infix ? happens
it wouldn't be a syntax error because if for whatever reason you had a Result (Result ... ...) ... you could technically actually want to do this :stuck_out_tongue:
Oh, yes. Damn, programming is wild. It’d be cool to get a warning if you’re doing this when you probably don’t want to.
For now, the ? binop operator is just syntax sugar. If/when we eventually convert it to a proper operator, we could definitely give a better error message if you do that
So this will be a common thing probably?
(Cmd.exec!("mv", [old, new]) ? FailedToSaveConfig)?
Nope, since ? does the early return
Oh, sorry, I must be drunk. I read that it’s like map_err but reading the sugar again I see that it’s “map_err?” :ok:
Yeppers peppers
Again, thank you Sam, it’s awesome to see how quickly Roc is moving thanks to these discussions and contributions! It’s lovely to read and try everything out! You and everyone else here is making me excited about programming in a way I haven’t been in years!
Luke Boswell said:
Any chance we could sneak this in with the current batch of changes too?
Let's officially make this the last thing though
That's good haha
It's getting pretty crazy (really, it's been crazy for over a week)
when it rains it floods!
Honestly kinda great to just get so much in
Agreed
9 messages were moved from this topic to #bugs > too few args by Luke Boswell.
Last updated: Jun 16 2026 at 16:19 UTC