(This might be specific to basic-cli@0.18.0.) I have a function that returns a custom Result, and during its construction I want to use Stdout.line!
for info. However, this causes a type mismatch where the Result the block returns conflicts with the Result that line!
returns, with no obvious way to deconflict them. What are some small/large changes I can make to this minimal reproduction to get it to compile?
app [main!] {
basic_cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br",
}
import basic_cli.Stdout
main! = \_ ->
s = try get_string! {}
Stdout.line! s
get_string! : {} => Result Str _
get_string! = \_ ->
Str.fromUtf8 [100]
|> \result ->
when result is
Err e ->
when e is
BadUtf8 foo bar ->
try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
Err "custom"
Ok s ->
Ok s
jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs$ roc check
── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────
This returns something that's incompatible with the return type of the
enclosing function:
16│ when result is
17│ Err e ->
18│ when e is
19│ BadUtf8 foo bar ->
20│> try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
21│ Err "custom"
22│
23│ Ok s ->
24│ Ok s
This returns an Err of type:
[Err [StdoutErr [
AlreadyExists,
BrokenPipe,
Interrupted,
NotFound,
Other Str,
OutOfMemory,
PermissionDenied,
Unsupported,
]], …]
But I expected the function to have return type:
[Err Str, …]
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warnings found in 28 ms.
jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs$
You need to use a tag error
a Str error can not merge with the tag error being returned from Stdout.line!
That or you need to map the Stdout.line!
error into a Str
Ah! Yes, Err (Custom "custom")
resolves the type mismatch.
Perhaps I need a different minimal reproduction for this that I'm hitting in my real app:
This returns an `Err` of type:
[Err [StderrErr [
AlreadyExists,
BrokenPipe,
Interrupted,
NotFound,
Other Str,
OutOfMemory,
PermissionDenied,
Unsupported,
]], …]
But I expected the function to have return type:
[Err [
FileReadErr * *,
FileReadUtf8Err * *,
], …]d
because I assumed those two tag unions
( [StderrErr ...]
and [FileReadErr ..., FileReadUtf8Err ...]
)
would merge.
Real app function:
read_file! : PathToFile => Result Str _
read_file! = \file_path ->
file_path
|> Path.read_utf8!
|> \result ->
when result is
Err e ->
when e is
FileReadErr path io_err ->
try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
try Stderr.line! tip_for_calling_this_app
Err e
FileReadUtf8Err path io_err ->
try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
Err e
Ok file_contents ->
Ok file_contents
jan@framey:~/_code/JanCVanB/roc-typesetter$ roc check
── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────
This returns something that's incompatible with the return type of the
enclosing function:
50│ when result is
51│ Err e ->
52│ when e is
53│ FileReadErr path io_err ->
54│> try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
55│ try Stderr.line! tip_for_calling_this_app
56│ Err e
57│
58│ FileReadUtf8Err path io_err ->
59│ try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
60│ Err e
61│
62│ Ok file_contents ->
63│ Ok file_contents
This returns an Err of type:
[Err [StderrErr [
AlreadyExists,
BrokenPipe,
Interrupted,
NotFound,
Other Str,
OutOfMemory,
PermissionDenied,
Unsupported,
]], …]
But I expected the function to have return type:
[Err [
FileReadErr Path.Path val,
FileReadUtf8Err Path.Path val,
], …] where val implements Inspect, val implements Inspect
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warnings found in 32 ms.
jan@framey:~/_code/JanCVanB/roc-typesetter$
app [main!] {
basic_cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br",
}
import basic_cli.Stdout
main! = \_ ->
s = try get_string! {}
Stdout.line! s
get_string! : {} => Result Str _
get_string! = \_ ->
Str.fromUtf8 [100]
|> \result ->
when result is
Err e ->
when e is
BadUtf8 foo bar ->
try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
Err e
Ok s ->
Ok s
jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs-2$ roc check
── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────
This returns something that's incompatible with the return type of the
enclosing function:
16│ when result is
17│ Err e ->
18│ when e is
19│ BadUtf8 foo bar ->
20│> try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
21│ Err e
22│
23│ Ok s ->
24│ Ok s
This returns an Err of type:
[Err [StdoutErr [
AlreadyExists,
BrokenPipe,
Interrupted,
NotFound,
Other Str,
OutOfMemory,
PermissionDenied,
Unsupported,
]], …]
But I expected the function to have return type:
[Err [BadUtf8 val val], …] where val implements Inspect, val implements Inspect
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warnings found in 25 ms.
jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs-2$
Yeah, this is a more nuanced issue
Essentially to be used in the when e is
block, e
must be either FileReadErr path io_err
or FileReadUtf8Err path io_err
This is a closed tag due to the when
e
is then returned
Suddenly, that closed tag is being merged with [StderrErr ...]
due to the try StdErr.line
Ayyy, nice, so adding _ -> Err e
seems to fix it.
I think you could add a _ -> crash "unreachable"
to your when e is
to fix it.
That or instead of returning e
, you need to return a new error.
FileReadErr path io_err ->
try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
try Stderr.line! tip_for_calling_this_app
- Err e
+ Err (FileReadErr path io_err)
Ooh that's nicer - especially since in real app functions this pattern is usually combined with error customization
This is an interesting sharp edge try
/?
when combined with when err is
Yeah, because I'm in a function that returns a Result
, when I see the typechecker message say "this function returns" I don't think "oh yeah when
is a function that returns a sometimes-closed union"
I read those typechecker messages as referring to the enclosing function's Result Err, but they're not.
JanCVanB has marked this topic as resolved.
I'm happy to continue discussing how to un-sharpen that edge here or elsewhere.
Side note - where in the typechecker messages does it indicate a closed tag union? I read all those ...]
-ending types as being open.
I think this is the only open union cause it is followed by d
:
[Err [
FileReadErr * *,
FileReadUtf8Err * *,
], …]d
That said, any union returned by a function is automatically made open (solves most headaches from open vs closed unions)
Anyway, sounds like we should at least improve a few error messages here.
What does ...]
indicate?
That it left out information cause it was unimportant for the diff
In this case, it elided the Ok
cases
Oh, gotcha. Yeah that's outside the Err tag union.
Eventually we plan to use ...
for open unions (and combining unions with ...OtherUnions
), but we don't do that yet.
Wow, so none of the above Err tags are open, then.
That makes sense. I think I was biased toward confusion here because I remember seeing that ...]
is the upcoming openness syntax.
I should've counted my matching [ ]
s more closely!
Wow, so none of the above Err tags are open, then
Except that all tags returned from functions are automatically made open. So it is kinda confusing.
Last updated: Jul 06 2025 at 12:14 UTC