In accordance with the 'scuttle the ship' philosophy, Austral programs abort immediately when trapping arithmetic overflows, with the message "Overflow in trappingOpname (TypeName)".
Yet Austral returns optional types from any memory allocation function rather than calling abort.
And stack overflow is a memory allocation failure, so why is the discrepancy? I.e. for the language focusing on correctness this is an unfortunate omission.
On the other hand none of popular or semi-popular system languages allows to explicitly control stack consumption. Zig has some ideas, but I am not sure if those will be implemented.
There is discrepancy because things the programmer can prevent are handled differently from things the programmer cannot prevent.
The programmer can always statically ensure that the program doesn't experience a trapped overflow, and that the stack size is not exceeded. All the information to do that is available when the programmer runs the compiler.
But there is no way to prevent a memory allocation failure when using `calloc`, since the information required to do that is not available when the programmer writes the code. In fact, when running on POSIX systems it's not even possible to check _in advance_ at runtime whether a memory allocation will succeed.
This is why Austral's allocateBuffer(count: Index): Address[T] returns an Address[T], which you have to explicitly null-check at run-time (and the type system ensures that you can't forget to do this).
Of course, on some non-POSIX systems such as seL4, the programmer can know at compile-time that memory allocations (untypedRetype) will not fail. When you use Austral on such systems, you don't have to use calloc/allocateBuffer at all.
To statically ensure a stack overflow does not happen requires that recursion is rejected by the type system. Austral does not do that so the stack overflow is a dynamic condition similar to memory allocation failures.
Maximum stack usage can be calculated in the presence of recursion. Tail calls can be handled as branches instead of nested call frames, but also non-tail calls are tolerable if you have (or can infer) some measure to determine maximum call stack depth.
It's a pain, and the type system rejecting any recursion is certainly simpler, but that's not a strict requirement.
Inferring the number of loop integrations or recursion levels is in practice impossible when the number depends on the user input.
For a system language I would like to see that when the compiler cannot infer the bound on the stack size or when that static bound exceeds some static limit, a function call is treated as fallible.
And stack overflow is a memory allocation failure, so why is the discrepancy? I.e. for the language focusing on correctness this is an unfortunate omission.
On the other hand none of popular or semi-popular system languages allows to explicitly control stack consumption. Zig has some ideas, but I am not sure if those will be implemented.