When I was doing today's AoC, I got an crash: Addion overflow error. This was annoying, since there is no line number on the crash and it took me some time to find the calculation what crashed the program.
In the end, the error was a logic error somewhere else. I called toLower = \e -> e - 'A' + 'a' multiple times on the same value. So the line number would have been nice, but not a real solution.
I like about Roc, that error handling is not done with try-except, and therefore I am not so happy, that each program, that uses an arithmetic operation can crash.
Last year, I proposed some sort of try-expect just for arithmetic operations but was told to use Num.XChecked functions, like Num.addChecked. This works, but it is not so nice to change all infix arithmetic operations with the checked versions:
1 - 3 to 1 |> Num.subChecked 3.
Does anyone else run into this kind of problem? Do you use the checked versions instead of the infix operations?
Is there a better solution to handle an overflow then just crashing the hole program?
Maybe a better crash message could contain the operation, the types and the values, like: You are trying to calculate 1 - 3 on a U8 value on Line XX. The lowest value on U8 is 0.
This is not an idea, but I hope, someone else has a good idea :wink:
I think that if we had checked math operators like Pony, and combined it with the fallible binding syntax it could work
Num.subSaturated and friends can be helpful in some cases. For doing advent of code puzzles based on grids, I was originally using unsigned integers for coordinates, but found it became tedious to handle the potential overflows at the edges of the grid, so I switched to signed integers (even when the coordinates are all non-negative numbers)
But if you are then coming from indexes you have to do the noisy U64 to I64 casting. Not the worst thing ever, but noisy
But that is largely what I do in geometric puzzles. I’ll
Have a Point type that’s a U64 pair and a Delta type that’s a I64 pair
And then move that conversion to functions that work on those types - and hope LLVM inlines them :grinning_face_with_smiling_eyes:
General question, if you got a proper stack trace that used good debug info and pointed to the exact line the overflow came from, should you still want to change things?
Does anyone else run into this kind of problem?
I also ran into it a couple times
if you got a proper stack trace that used good debug info and pointed to the exact line the overflow came from
Sounds good to me
Brendan Hansknecht said:
General question, if you got a proper stack trace that used good debug info and pointed to the exact line the overflow came from, should you still want to change things?
That probably is sufficient
Today, I got this crash again. A proper stack trace would probably help.
I think my question is more general. I don't know so many languages. I don't know, if other languages have a better solution. Is there really no good solution for this problem?
In python 1-3 does not crash, since it automatically changes the type. And python has a fallback to a bigInt that can handle practically all numbers. But this is not possible for Roc with its unboxed numbers.
In Go, 1-3 does work, since it silently wraps. I would say, that is worse then a crash. 1-3 == 254 should not be true, only if you explicitly want this.
Is there another language, that has another solution then to crash?
There's only really three options with fixed number types: Overflow, Crash, Or Check and return result
One problem you could say is Roc, being purely functional - only has the concept of Crash. There is no panic/recover like Go, and that's largely because Roc is executed (semantically at least) in a single synchronous thread (though of course the platform is probably doing a lot of different multi-threaded operations for somethings).
But for overflow/underflow with fixed types what do you want? If it recovers, it will hit the same bug again and your program may not terminate. I think you either want checked math (or roll it yourself with bounds checks) or you want a crash report with good diagnostic information.
Also saturated math is an option. One thing that might help a bit is fully embracing non-panicking math by giving them inline symbols. Like how zig does.
Theoretically, we are equivalent to zig with release safe today.
Eh, but no catch for a panic
Though I'm not sure I have even seen someone catch a panic in zig, but it must happen, right?
I think it would be nice to add 3 operators to roc like so:
+| inline add saturated
+% inline add wrapped
+? inline try of add checked
That plus eventual good debug info such that crashes can give nice code locations would help a lot.
Oskar Hahn said:
Is there another language, that has another solution then to crash?
I have only seen crashing and wrapping in practice for languages that care about performance. That said, I think one of the creators of go mentioned that he thinks sized integers with wrapping semantics in go was a mistake. One of the most common sources of security bugs. Also, he mentions that big ints with inline optimizations are generally quite fast.
That said, I think the concern is more memory than speed when you want a list u8 and instead it has to be a list of inline big ints. I guess you could make an exception for bytes, but in general you would be giving up on memory usage all over the place.
Yeah Pony has checked math symbols as well, though it has different semantics (since they use a singular untyped error for all failures (and partial functions).
Brendan Hansknecht said:
That said, I think the concern is more memory than speed when you want a list u8 and instead it has to be a list of inline big ints. I guess you could make an exception for bytes, but in general you would be giving up on memory usage all over the place.
Would it make sense to have builtins for casting List (Num a) -> List (Num b) that could do that without an additional allocation when refcount=1? Would that be enough juice for the squeeze?
Maybe a better crash message could contain the operation, the types and the values, like:
You are trying to calculate 1 - 3 on a U8 value on Line XX. The lowest value on U8 is 0.
This would also probably be a good idea at least until we get good debug info and traces at runtime. Though might be quite hard to do in practice. I dont think line info makes it anywhere near this stage in the compiler and if it did, we probably could just fix debug info.
Would it make sense to have builtins for casting
List (Num a) -> List (Num b)that could do that without an additional allocation when refcount=1? Would that be enough juice for the squeeze?
Due to the elements being different sizes, would likely be a new allocation anyway. Also, map should already use a single allocation if it is valid. (Though need to double check if it covers as many cases as possible).
So this should be fine as List.map Num.intCast which could be used to use `I64 during Advent of code if wanted.
Last updated: Jun 16 2026 at 16:19 UTC