Hey all - I've got a real puzzler on my hands. I have been trying to port some old AoC code to the new compiler. However, while the program passes roc build, it crashes before reaching the end of main. However, depending where, and how many dbg statements I place, the crash will occur at different places.
Also, I get some strange behavior where placing two dbg statements for the same Try value will show as the wrapped value once, and an Ok(...) the next time.
There is a third issue, which seems unrelated to the dbg issue, in which I can pipe the results of the part1() calculation to Stdout.line!(), but if I save the result of part1() as a value and then call Stdout.line!() on the value in the next line, the program crashes before reaching the Stdout.line!().
Finally, the program never finishes executing completely. It will always crash before completing, but depending on how I structure the rest of the code, I may be able to get it to print the result of part1().
I have been trying to minimize this issue, but haven't been able to due so yet.
(current code coming below)
I will give the full code below, but let me start by giving this snippet. Depending on which of these debug statements are uncommented, the program will crash in different modes, and may or may not make it back to main to execute the Stdout.line!() statement in main.
## Solve part 1
part1 : Str -> Try(Str, _)
part1 = |s| {
s.split_on("\n")
.map(extract_digits)
->keep_oks(compute_calibration)
# ->debug() # (1)
->sum()
# ->debug() # (2)
.to_str()
# ->debug() # (3)
->Ok
# ->debug() # (4)
}
Failure Modes
debug statements commented out:Roc crashed: Internal error: TypeMismatch in call_invoke_closure continuation
(1) uncommented, or (1) and (2) uncommnted:thread 43720 panic: reached unreachable code
Unable to dump stack trace: debug info stripped
Aborted (core dumped)
(2), (3), or (4) uncommented:(1) and (3) uncommented:(1) prints, but prints an empty list, even though not empty. Dbg (3) does not print. The function completes successfully, and returns to main, where depending on main, the result may print via Stdout.line!(). Finally, the program crashes with a different error message:dbg: []
209.0
Roc crashed: call_invoke_closure: value_stack empty when popping function
(1) and (4) uncommented:(1) prints, but prints an empty list, even though not empty. Dbg (4) does not print. The function completes successfully, and returns to main, where depending on main, the result may print via Stdout.line!(). Finally, the program crashes with a different error message:dbg: []
209.0
Roc crashed: non-exhaustive match
(1) uncommented, and both (3) and (4) uncommented:(1) and (3) print, but (1) prints an empty list, even though not empty. Dbg (4) does not print. The function completes successfully, and returns to main, where depending on main, the result may print via Stdout.line!(). Finally, the program crashes with a different error message:dbg: []
dbg: "209.0"
209.0
Roc crashed: non-exhaustive match
(1) and at least one of (3) or (4) uncommented, uncommenting (2) does not change the failure mode. However, instead of dbg (2) printing an integer value, it also prints an empty list as well. So uncommenting all results in the following:dbg: []
dbg: []
dbg: "209.0"
209.0
Roc crashed: non-exhaustive match
Note that debug is defined as:
debug : a -> a
debug = |v| {
dbg v
v
}
I'll come back and document some of the other issues later, but for now, here is the full code file:
app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.6/2BfGn4M9uWJNhDVeMghGeXNVDFijMfPsmmVeo6M4QjKX.tar.zst" }
import pf.Stdout
main! = |_args| {
input =
\\two1nine
\\eightwothree
\\abcone2threexyz
\\xtwone3four
\\4nineeightseven2
\\zoneight234
\\7pqrstsixteen
part1(input)?->Stdout.line!()
# answer1 = part1(input)?->debug()->debug()->debug()
# dbg "answer"
# Stdout.line!(answer1)
Ok({})
}
## Solve part 1
part1 : Str -> Try(Str, _)
part1 = |s| {
s.split_on("\n")
.map(extract_digits)
->keep_oks(compute_calibration)
->debug() # (1)
->sum()
# ->debug() # (2)
.to_str()
# ->debug() # (3)
->Ok
->debug() # (4)
}
extract_digits : Str -> List(U64)
extract_digits = |s| {
s.to_utf8()
.keep_if(|c| c >= '0' and c <= '9')
.map(|c| (c - '0').to_u64())
}
compute_calibration : List(U64) -> Try(U64, _)
compute_calibration = |digits| Ok(List.first(digits)? * 10 + List.last(digits)?)
## Solve part 2
part2 : Str -> Try(Str, _)
part2 = |s| {
s.split_on("\n")
.map(extract_text_digits)
->keep_oks(compute_calibration)
->debug()
->sum()
.to_str()
->Ok
}
extract_text_digits : Str -> List(U64)
extract_text_digits = |s| {
bytes = s.to_utf8()
(0).to(bytes.len())
.fold(
[],
|ds, i| {
match bytes.drop_first(i) {
['o', 'n', 'e', ..] => ds.append(1)
['t', 'w', 'o', ..] => ds.append(2)
['t', 'h', 'r', 'e', 'e', ..] => ds.append(3)
['f', 'o', 'u', 'r', ..] => ds.append(4)
['f', 'i', 'v', 'e', ..] => ds.append(5)
['s', 'i', 'x', ..] => ds.append(6)
['s', 'e', 'v', 'e', 'n', ..] => ds.append(7)
['e', 'i', 'g', 'h', 't', ..] => ds.append(8)
['n', 'i', 'n', 'e', ..] => ds.append(9)
['z', 'e', 'r', 'o', ..] => ds.append(0)
[d, ..] => if d >= '0' and d <= '9' ds.append((d - '0').to_u64()) else ds
[] => ds
}
},
)
}
keep_oks : List(a), (a -> Try(b, e)) -> List(b)
keep_oks = |vs, f| {
vs.fold(
[],
|acc, v| {
match f(v) {
Ok(u) => acc.append(u)
Err(_) => acc
}
},
)
}
sum : List(Num) -> Num
sum = |nums| nums.fold(0, |acc, n| acc + n)
debug : a -> a
debug = |v| {
dbg v
v
}
I'll look into this!
@Ian McLerran I have a fix in https://github.com/roc-lang/roc/pull/8752/changes - just cleaning it up now
tl;dr stack corruption in the interpreter - dbg was not really related, it just triggered the bug because involved the stack :smile:
Ahh, that makes sense! Changing the debug calls just changed the stack, hence the different results.
Ian McLerran has marked this topic as resolved.
Ian McLerran has marked this topic as unresolved.
Thanks for tackling this @Richard Feldman! I built off the latest main, and I can see that most of the failure modes are fixed! (I can remove all the debugs, and the program compiles, regardless of whether I print from the pipe, or save the value and print)
Looks like this code also raises a couple other bugs if I reintroduce some debug() calls:
1) If I reintroduce (1) and (2):
- debug (2) prints an empty list, where it should print the integer value "209"
2) If I reintroduce (2), (3), or (4) only:
- crashes with: Roc crashed: Internal error: TypeMismatch in call_invoke_closure continuation
ah interesting! I'll follow up!
Seems like the behavior of this code has changed since 20 days ago. Now when I build, regardless of dbg statements included (or not included), the program will build, but then crashes at run time with:
Roc crashed: Error evaluating: NotNumeric
I will minimize this and make an issue
#9020 I have described a workaround there as well
Thanks Anton!
@Anton Great work chopping that thing down! I was trying to do the same but did not get nearly this far! :sweat_smile:
Just looking at your reproduction and work around... I see your repro was using a local closure, and the work around is to move the local closure to a top level closure.
It looks like the local closure f in your code corresponds to compute_calibration in my code, but in my code, compute_calibration is top level, not local... Seems like there might be something else going on here?
Yeah, it seems like a complex interaction, I have included your original code as well to make sure it works when the issue appears to be fixed.
Last updated: Feb 20 2026 at 12:27 UTC