Array indexing is a footgun and ? is here to help you
- And TypeScript is unsound
- And TypeScript doesn't seem to care that it's unsound
- Sound programming languages love runtime errors
- Back to the initial review
A short look at why array indexing bugs happen, and ?uestioning why languages don't do more. I need you to use the nearest question mark.
Imagine reviewing this code, a normal and good thing to do, and finding nothing wrong. Imagine this being one of a handful of changes in different files. My money is you don't find the bug.
Personally, I think my brain would believe that the first bounds check
result.content.length > 0 was a refinement, and that the code was working
as it should. Workload goes up, review quality goes down.
I like Rust and I like TypeScript but in different ways and for different reasons.
I like Rust because of P̸̡̿a̸̮̋ṛ̷͝s̴̺̉͝e̸͕̖͆̕ ̴̪̐́Ḍ̶̹̒o̵̲̹͊̽n̷͕͂'̸̨̉t̸̖̚ ̵̕͜Ṿ̴̬͛a̴̤̍̕l̶͇͘i̴̻̐d̷̙̣̈a̶͕͛͛t̶̬̀͘ē̶̲̟, Nominal Types, and great error handling.
But is it really unusable?
Yes it is extremely unusable.
- Having more than 50 lines of code makes it effectively write-only.
- Every person added to a project introduces bugs at an increasingly exponential rate.
TypeScript doesn't have those same properties. As long as you are diligent about
your design, use types everywhere, and turn on all the
strict checks the compiler has,
you can keep adding people and have (slightly sub-)linear increases in productivity.
But TypeScript has so many paper cuts. I'm figuratively bleeding.
And TypeScript is unsound
TypeScript is wildly unsound, and it will let you do this:
// Function calls don't invalidate refinements
And TypeScript doesn't seem to care that it's unsound
Here's TypeScript just throwing its hands in the air:
During development of this feature, we discovered a large number of inherently unsafe class hierarchies, including some in the DOM. Because of this, the setting only applies to functions written in function syntax, not to those in method syntax.
The reason I know about this is because I managed to run into it very early in my TypeScript "career".
Sound programming languages love runtime errors
I don't like runtime errors. I'll be clear and state that I'll take a runtime error (or panic) over unsoundness every day of the week, but I don't have to like it.
C programmers claim that they can allocate and deallocate memory manually and that it's "fine, actually" (it's not). Rust and TypeScript programmers claim that they can decide when to check array lengths before accessing elements and that "it's fine actually" (it is fine actually) — BUT I DON'T LIKE IT.
There is no reason why the
? operator can't just solve this. Both of them.
The type signatures will be different, but that's ok with me.
I personally think array indexing shouldn't lead to runtime crashes except
!. In Rust it could be argued that you could skip
the checking step in
unsafe code, but
get_unchecked already exists so
big shrug, I guess.
Since TypeScript already has (broken) refinement, it could just allow infallible array indexing when it can prove that the access will be in bounds. Maybe Rust can get that too, some day. Refinement is really neat, and I wish it wasn't broken in TypeScript.
Back to the initial review
Load bearing code —
if result.content.length > 0 if result.content.levelId
The length check doesn't wrap the array indexing call.
I easily miss details like this, and I would expect the compiler to have my back. It does, in a sense, have my back (it crashes), but I resent that I have to find this error in Sentry. 2500 crashes in one week, just as everyone is enjoying their vacation.
It's not a big deal. It really isn't. But if I could choose, I think I'd like to have a compiler that didn't let me crash unexpectedly.