I'm out of the loop regarding the status of ? in Roc so please take this for what it is and excuse my ignorance.
My context is Ruby (so ! as effectful makes sense), Haskell, Elm, and Rust (so ? as error handling makes sense). If I want to perform an effect that may error, and I want to early-return said error, I'd expect to write fireZeMissiles!? (meaning (fireZeMissiles!)?). If I want to map the error, I'd expect to write fireZeMissiles!? MissileMalfunction.
Perhaps that's not feasible, but it's what I naturally just thought "yeah, that's how it probably works".
# My understanding of the proposal:
userParam
|> try Str.toU64 ? ParamParseError
|> try getUser! ? UserLookupError
# vs. what I thought it'd be:
userParam
|> Str.toU64? ParamParseError
|> getUser!? UserLookupError
Without mapping the first error:
userParam
|> try Str.toU64
|> try getUser! ? UserLookupError
# vs. what I thought it'd be:
userParam
|> Str.toU64?
|> getUser!? UserLookupError
Effectful error mapping:
userParam
|> try Str.toU64 ? ParamParseError
|> try getUser! ? \e -> logAndReport! UserLookupError e
# vs. what I thought it'd be:
userParam
|> Str.toU64? ParamParseError
|> getUser!? \e -> logAndReport! UserLookupError e
Passing in the piped arg as context:
userParam
|> try Str.toU64 ? ParamParseError userParam
|> try \id -> getUser! id ? UserLookupError id
# vs. what I thought it'd be:
userParam
|> Str.toU64? ParamParseError userParam
|> \id -> (getUser! id)? UserLookupError id
I appreciate that this is extremely terse and therefore easy for my brain to parse (fewer keywords and chars are usually easier for me, as long as there aren't more operators than I can keep in my head (Haskell)).
I saw !? mentioned as an operator, but in my suggestion above ! and ? are separate and the fact that they may appear next to each other is just a coincidence. When explaining them to someone else I wouldn't intermingle them at all.
To me it's visually easier to grasp that |> getUser!? pipes to getUser!, than that |> try getUser! pipes to getUser!. Probably because with try I am piping "past" something.
When promoting Roc to others I find that error handling is one of the topics that gets the most interest. It's a high-pain area while also being easy to explain. Many people like Rust's ? and I'd like to say "Yes, Roc has ?. It's got the best of Rust _and_ it removes the verbosity of defining error structs for every little variation, because you can just _make errors up_ and the compiler will ensure you're dealing with them." Now, try is essentially ? from Rust, but wouldn't it be nice to be able to sell those Rustaceans on familiarity by having exactly ??
This may be completely infeasible syntax-wise, or totally irrelevant as I've missed previous discussions. If so, sorry! Hopefully it's helpful as another data point for "this is what my brain would expect" as that usually helps with design.
Bonsoir!
Yeah, !? has been discussed a lot with various options. For example in #ideas>what if we leaned into the ridiculousness of `!?`
This is an alternative that is explicitly avoiding !?.
PS. I've been writing a lot of Rust lately and coming over to Roc for a while, and seeing this proposal, is such a breath of fresh air. Thanks for working on this! Roc is the language I have the most fun writing and it's just _incredibly great_.
@Brendan Hansknecht I guess the only novelty in my suggestion would be the error mapping then? foo!? FooError doesn't seem to have been discussed? Although Richard did have a strong reaction against !? so perhaps it's doomed :joy:
Personally, I think that would be confusing cause FooError looks to be an arg to foo. It looks like you are passing an Enum of value FooError to foo.
Specifically Str.toU64? ParamParseError userParam. That looks like 2 args passed to Str.toU64. Relatedly, if you saw Str.toU64? userParam is userParam a function to map to error or is it an arg for Str.toU64
Fair point! try foo! ? FooError avoids that completely at the cost of verbosity. I, personally, would prefer foo!? FooError just to save myself some typing/mental parsing, but the longer version is probably easier to teach :)
If I know that ? is early-return (like Rust) and that , on top of that, a function cannot contain ? in its name, then it'd be abundantly clear to me that Str.toU64? userParam is error handling. But this is optimizing for someone who knows the language, rather than someone who is learning, for sure.
Note, I don't think try is specifically required for your idea. Just the postfix ?
So it would be Str.toU64 userParam ? ParamParseError and Str.toU64 userParam ?
And also foo! ? FooError or foo! someArg ? FooError
Indeed! I just cut the space to make it 1:1 with Rust
I'm willing to sacrifice a space for the greater good. If people find foo! ? FooError and foo! ? more palatable, then I'm all for it! :)
Oh, I recall one of the issues with only using ?:
observedFileData =
try Tracing.span "get-file-data" \{} =>
File.read! "some-file.txt" ? FileReadErr
would become:
observedFileData =
Tracing.span "get-file-data" \{} =>
File.read! "some-file.txt" ? FileReadErr
?
# maybe would require parens for clarity:
observedFileData =
(Tracing.span "get-file-data" \{} =>
File.read! "some-file.txt" ? FileReadErr
)?
Which is pretty strange looking
Don't do this to me Brendan, we were so close...
haha, yeah, that is what I think all of the time. Apparently mixing postfix operators with space based function calling leads to messes pretty easily.
And now I'll go full-circle to ?> I guess :joy:
It is indeed nice to have the "try" mechanism at the start of the line
Brendan Hansknecht said:
Should it be changed to double ? In the type?
??
I suggested this in the doc as an alternative, I'm down but people weren't as big on the look compared to alternatives/the current syntax.
Also, ?? means "extract or default" at the moment, so ?? also meaning "type of defaultable field" would be close but different
I'd be okay with ?> if we always tried to keep it on the same line
yeah, but it is a lot closer than ? which will mean "map the error type of a result".
Sometimes it should go to a different line, but it should be preferred to be a one liner
Wait, I thought the suggestion was to use ?> for mapErr
Is that wrong?
I was thinking ?> Str.toU64 [SOME OPERATOR] ParamParseError
So ?> would be early-return
Oh nvm
Yeah, not ideal
Is there some discussion about that?
The > implies piping in Roc
Yeah, in my mind it's "we're happily piping with |> and then if there's danger we use ?>". So if I look at a long pipe I'll see the places where it can early-return right away:
|> eat
|> breathe
?> drinkMaybePoison ? ImminentDeathError
|> rest
?> runWithScissors ? CatastrophicBleedingError
So ?> means piping, but can you also use it for a single expression?
Cause then it kinda means two things
You win again, Batman
Seriously, idk
Hey man, I've been in this cave thinking about this for like 3 weeks
There is no silver bullet, I think. Every option is just less bad than another
Can you give an example where try is used for a single expression? for inspiration and research purposes only
fileContents = try File.read! "file.txt" ? FileReadErr
fileContents ?= File.read! "file.txt" ? FileReadErr
fileContents <? File.read! "file.txt" ? FileReadErr
no
<? reads nicely, but it's weird
Yeah, I really don't mean to dissuade your attempts at creativity! It's just a hard problem, so I'm giving terse answers to questions I'm frustrated have no good solution
No problem at all mate, I'm having fun
41 messages were moved here from #ideas > try-else error context adding syntax by Brendan Hansknecht.
Last updated: Jun 16 2026 at 16:19 UTC