well-typed programs never go wrong
“Go wrong” is an informal expression and you need to understand what it means in this context. It doesn't mean that programs written in type-safe languages don't have bugs. No type system is going to flag this code (in a fictional language) as wrong:
let add_one (x : int) = (x + 2) : {y:int | y = x + 2}
The specification contradicts the function name, but that's beyond the power of a type system, even one that can specify that the return value of the function has an arithmetic relationship with the argument.
A more precise statement would be that well-typed programs have well-defined behavior. Or, in other words, the semantics of well-typed languages does not include cases of undefined behavior.
The defined behavior can be to signal an error at runtime. The point of type safety is that you're guaranteed an error, as opposed to really bad effects such as arbitrarily corrupting memory. At one extreme, with a purely dynamic type system such as Lisp or Python, any program might suddenly stop with a runtime type error at any point. Languages with a static type system restrict the places where a runtime error can occur. The more expressive the type system is, the fewer places a runtime error can occur. Due to Rice's theorem, you can't have a nontrivial type system that both allows all computable functions and has no runtime errors.
Array indexing is usually by int, but we can freely send out of bound indices or even negative ones. In these cases we get exceptions if we're lucky or crash
You don't only get exceptions “if you're lucky”: getting an exception from an out-of-bounds array index is guaranteed. It's part of the semantics of the array indexing operator: a[i]
returns the element at position i
, or raises the IndexOutOfBounds
exception of i
is not a valid position. This is different from unsafe languages such as C¹ which cannot guarantee that a
is an array and, even if it is, cannot keep track of its size.
Pointer arithmetic is purely chaotic in terms of "going wrong" yet they are type-safe if we disallow casting
Pointer arithmetic is array indexing seen from another angle. There are many language features other than casting that can break pointer arithmetic. For example, in C, void *
conversion allows the programmer to lie freely about the type of a pointer.
Type-safe languages generally don't allow the programmer to build a pointer. You only have access to objects defined by the language.
Some program that wouldn't go wrong are not well-typed
That's true, but irrelevant. The claim is about well-typed programs. It doesn't say anything about programs that are not well-typed.
It is true that well-typed terms in STLC don't go wrong, but that's because STLC is total
Yes, “total” is one way to phrase progress.
and normalizes
No, normalization is irrelevant. STLC plus a recursion combinator is still type-safe.
and wow
I have no idea what this is supposed to mean.
¹ C as specified in theory or C as implemented in practice. It's theoretically possible to make a C implementation that detects all runtime errors, but it would be extremely inefficient.