Unconventional Ways to Cast in TypeScript

(wolfgirl.dev)

62 points | by Bogdanp 9 hours ago ago

27 comments

  • culi 3 hours ago

    This post is trying to solve a problem you should never have to solve. The function in the post:

      const cast = <A, B,>(a: A): B => a as unknown as B;
    
    should never be used in a professional codebase. The post even admits (though, in my opinion, understates) as much:

    > If you're holding it right, these things don't come up, and your code genuinely is much much safer than if you used raw Javascript.

    Just wanted to highlight this point I feel needs to be underscored

    • jmull 38 minutes ago

      > should never be used in a professional codebase

      Mostly true.

      But most system also ingest external data -- that data truly is "unknown" until you parse/validate it. Once you do parse/validate it, you're going to want it to have the correct type.

      You shouldn't "never" use. You should only use it in limited, constrained ways, and always with other controls that are sufficient to ensure it's correct.

    • mpeg 3 hours ago

      Eh it's ok to use `as unknown as X` sometimes

      If you have complex types, it's sometimes the easiest way to do what you want, and it's perfectly safe as long as you are 100% sure that the types are compatible.

      For example, where you have a fluent-style API where each method modifies the types it's unavoidable to end up using that kind of cast

      • eyelidlessness 2 hours ago

        It’s not even safe if you’re 100% sure the types are compatible, unless you’re also 100% sure nothing will change that fact. The reason it’s unsafe is because it suppresses the type error permanently, even if whatever factors led to your certainty now change anywhere upstream ever.

        There are certainly ways to guard against that, but most of them involve some amount of accepting that the type checker produces errors for a reason.

        • mpeg 2 hours ago

          Yes of course the types could change in the future, and the forced cast might cause issues. I wish there was a better way, but this is an acceptable tradeoff.

          Bear in mind, most changes that could cause issues will still be caught by the type checker in whatever object you're casting to. Obviously it should not be overused where not needed, but it's almost always used in fluent apis because there's no better way (that I know of, at least)

      • aniviacat 3 hours ago

        > it's perfectly safe as long as you are 100% sure

        That was funny to read

        • mpeg 2 hours ago

          If you disagree, you're welcome to prove me wrong!

          To give you an example from a popular open source ts-heavy project:

          https://github.com/elysiajs/elysia/blob/94abb3c95e53e2a77078...

          The `return this as any` there, which effectively casts it to the same type this had, but with the added get route is perfectly safe, it works, and will never be a problem by itself.

          • lovich 2 hours ago

            It’s funny because the reason you used a language like typescript is because you want the compiler to be 100% sure that it’s compatible, not relying on human reasoning.

            If you were going to rely on that anyway, why not just use JavaScript as is and avoid the boilerplate from typescript

            • mpeg 2 hours ago

              The code I linked for example results in a web router that is fully type safe.

              It's not like using js at all, not that I think there's anything wrong with it, if that's your jam.

            • horsawlarway an hour ago

              This is a great example of "letting perfect be the enemy of good enough".

              Typescript is "good enough" at it's job. That's a great reason to use it.

  • fenomas 4 hours ago

    Funny, just yesterday I found myself casting in a way I'd never seen before:

        const arr = ['foo'] as ['foo']
    
    This wound up being useful in a situation that boiled down to:

        type SomeObj = { foo: string, bar: string }
        export const someFn = (props: (keyof SomeObj)[]) => {}
    
        // elsewhere
        const props = ['foo'] as ['foo']
        someFn(props)
    
    In a case like that `as const` doesn't work, since the function doesn't expect a readonly argument. Of course there are several other ways to do it, but in my case the call site didn't currently import the SomeObj type, so casting "X as X" seemed like the simplest fix.
    • pverheggen 4 hours ago

      Why not use annotation instead?

        const props: ['foo'] = ['foo']
      • fenomas 3 hours ago

        Didn't occur to me, that's certainly more defensible! Though maybe less humorous.

      • halflife 3 hours ago

        Or: cons foo = [‘foo’] as const;

        • wk_end 3 hours ago

          > In a case like that `as const` doesn't work, since the function doesn't expect a readonly argument.

        • cat-whisperer 2 hours ago

          I don’t get this? why do I need to say as const?

          • afdbcreid 2 hours ago

            `as const` is a special annotation that lets the TypeScript compiler infers the more specific type `["foo"]` instead of `string[]`.

    • c-hendricks 4 hours ago

      I generally put lint rules to prevent casting, why cast here instead of declaring `props: (keyof SomeObj)[]` or `props: Parameters<typeof someFn>[0]`?

      • fenomas 4 hours ago

        Er, my justification was that the code in question was meant to be minimally demonstrating someFn, and adding an import or a verbose type seemed to distract from that a little.

        But mostly it just gave me a chuckle. I tried it because it seemed logical, but I didn't really think it was going to work until it did..

    • NathanaelRea 2 hours ago

      You could also do

          const arr = ["foo" as const]
  • Octoth0rpe 5 hours ago

    Sigh, the abuse of void was particularly eye-opening for me. If one really must do this - and I can think of a couple of cases where one might mostly around progressively porting old codebases to typescript - I'd strongly prefer the simple `a as unknown as B;` as one can easily grep for ` as unknown as ` to find your crimes.

  • beezlewax 2 hours ago

    Can't you change settings in ts to make it more 'strict' than this ?

  • metadaemon 3 hours ago

    Surprised the `satisfies` operator wasn't called out

  • worik 3 hours ago

    This why I find Typescript frustrating.

    It really should be called "vaguely typed script"

    • culi 3 hours ago

      it's gradually and structurally typed and I think that's what makes it great. I also disagree that it's vague. Nowadays you can even have typesafe regex

    • shepherdjerred 2 hours ago

      TypeScript is incredibly safe if you use strict tsc settings and ts-eslint strict presets.

      If it were this strict out-of-the-box, it probably would have been hated since most devs don't really want to deal with static typing.

      https://typescript-eslint.io/getting-started/typed-linting

    • jakubmazanec an hour ago

      On the other hand, you can run Doom in TypeScript's type system.