Unconventional Ways to Cast in TypeScript

61 pointsposted 9 hours ago
by Bogdanp

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

28 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

2 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

3 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

jakubmazanec

an hour ago

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