3 ms·
There’s a seamless ability to add runtime checks in TypeScript and has been for a long time: type guards. It’s not built into the type system because no one act
by eyelidlessness 4y ago
There’s a seamless ability to add runtime checks in TypeScript and has been for a long time: type guards. It’s not built into the type system because no one actually wants that in TS or any other language, even when they think they do, at least without control over when it’s used. That is type guards.
What everyone who asks for runtime checking actually wants is:
- define once
- declaratively
- with clear validation and deserialization at boundaries where data types aren’t known
- without overhead for internal types which are well known
- without an additional build step like codegen
That is type guards! They just have to be granular and composable, and well tested at a granular and composable level. Libraries like zod and io-ts have shown how to do it. You can just use them, or many others like them, you don’t need anything new. Or you can pretty trivially build your own custom solution if they don’t scratch your particular itch.
You aren’t missing seamless runtime checks. You’re just not using them.
- yazaddaruvala 4y agoThe GPs request is a very fair one. Typeguards are 10% of starting the solution. What everyone wants: Autogenerated type guards by class. What everyone gets: “Remains out of scope.” and need to use other libraries. The compiler can do this significantly better (more ergonomic, less time, etc) than 3rd party libs. TS devs really should reconsider this feature. https://github.com/microsoft/TypeScript/issues/15265 https://github.com/microsoft/TypeScript/issues/15265
- eyelidlessness 4y ago> What everyone wants: Autogenerated type guards by class. I’m quite sure people are asking for that, but I’m quite sure they’d regret wanting it if they got it. To the extent it’s desirable, it’s a thing that should be in TC39, not in TypeScript. The wailing and gnashing of teeth over when or whether or how type coercions happen is already mythical for the underlying language. Adding it to a compiler that’s increasingly not used to compile but only to type check is gonna add ten years of gripes about JS tooling nonsense to every single discussion. They have considered this feature. Type guards are the solution and they’re a really good solution given the circumstances.
- gajus 4y agoThere are some very legitimate use cases for runtime validation, namely when dealing with external inputs/databases, e.g. https://github.com/gajus/slonik#user-content-runtime-validation https://github.com/gajus/slonik#user-content-runtime-validat... Flow had [flow-runtime](https://github.com/gajus/flow-runtime https://github.com/gajus/flow-runtime), which was nice. There were/are a few half-baked solutions in TS ecosystem, but nothing as mature as flow-runtime was. However, I wish that TS supported this natively.
- winter_blue 4y agoHmm, I was reading the doc[1], and it seems like type guards aren't run-time checks though; they're a way to write a function that checks a given type and then narrows that type to another more specific type. I think what OP might've meant by run-time checks is auto-generating code that wraps every unpredictable/non-guaranteed boundary with a run-time type check. So if you have some data with the "any" type data being turned into a type or a use of the "as" keyword (e.g. `someType as anotherType`), then add a run-time check that throws an error at that boundary (instead of the code erroring out elsewhere, or resulting in incorrect behavior). Something like [1] https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards https://www.typescriptlang.org/docs/handbook/advanced-types....
- eyelidlessness 4y ago> Hmm, I was reading the doc[1], and it seems like type guards aren't run-time checks though; they're a way to write a function that checks a given type and then narrows that type to another more specific type. That’s a runtime type check! Any equivalent you might bake into TypeScript would do the exact same thing, but it would do it rigidly in ways people don’t expect (being strict about input types, ie “do what I say not what I mean) or rigidly in ways people don’t expect (“do what I mean as long as you mean exactly what we allow”). Again, fine grained type guards as implemented by zod/io-ts/etc solve this problem and either give you all you need already or demonstrate exactly how you can use existing language features to achieve exactly what OP and anyone else asking for runtime types wants.
- mcluck 4y agoI agree and I'd like to add that if someone wants to throw an error for invalid type casts, they can use assertion functions. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions https://www.typescriptlang.org/docs/handbook/release-notes/t...
- eyelidlessness 4y agoThat’s an option I considered mentioning, but it has some weird ergonomics that many people won’t like.
- anderskaseorg 4y agoType guards in TypeScript are not sound: the language makes no effort to prevent you from defining an incorrect type guard that, when used, narrows your variables to incorrect types. Your mistake won’t be caught at compile or at runtime (until the variable is actually used in a way that’s incompatible with its declared type, which may be much later).
- MrJohz 4y agoType guards (X is number) are different from type assertions (X as number). The former are user defined runtime checks - if they are not correct, then the developer who wrote them is at fault. Moreover, they are typechecked, so you couldn't have a type guard that claims to return a number but actually returns an array, at least not without bypassing the Typescript checker some other way. Type assertions match what you describe better - they are ways to override the compiler's opinion about the type of a variable or expression. They are, as you say, unsafe in the sense that the compiler has no way of knowing if this assertion is true or not.
- matt_kantor 4y ago> they are typechecked, so you couldn't have a type guard that claims to return a number but actually returns an array, at least not without bypassing the Typescript checker some other way Sadly this isn't true: https://tsplay.dev/WvGArw https://tsplay.dev/WvGArw
- MrJohz 4y agoGood point, I wasn't 100% sure on that and should have checked myself. I'd say there's still a lot of value in having type guards as explicit places that can be heavily checked, e.g. with unit tests, to form strong boundaries to the rest of the code you right.
- hermanradtke 4y agoType guards are the primitives one needs to satisfy the compiler. No one wants to hand roll a JSON parser using those type guards. Yes, we have libraries like io-ts and zod. Having all those different libraries means my own library may export zod schema’s while your application uses runtypes. I understand that adding something like zod is out of scope for tsc. However, type guards alone are not the full answer either.