Typescript “casts” are not casts

« back

September 16, 2021 | Originally published at https://medium.com/ingeniouslysimple/typescript-casts-are-not-casts-4c6c954749c4?source=friends_link&sk=7696681eb8c2871e1bd0d8d69b844dab

At Redgate we recently ran an internal typescript training course, and I wanted to highlight a common misconception I saw a couple of times.

To explain what I mean, let’s compare some similar-looking code in C# and typescript. In C#, we can use the as operator (sometimes called a “safe” cast) to check the type of an object:

public void HandleEvent(object sender, Event event) {
    var ourEvent = event as OurCustomEventType;
    if (ourEvent != null) {
        // ... handle event
    }
}

Alternatively, we can also use a cast expression (or “explicit” cast) to throw an InvalidCastException if the cast fails:

var publishedDate = (DateTime)metadata["publishedDate"];
Console.WriteLine("Document published on " +
    publishedDate.ToString("MMMM dd, yyyy"));

The important part is that these casts happen at runtime: the C# compiler emits instructions to:


Typescript, however, does none of these things. If you’re coming to typescript from another language, you may be surprised to find out that your intuition about casts doesn’t apply here.

While typescript has syntax that looks like casts, these are actually “type assertions” instead. Let’s take the first example, and write something like it in typescript:

const handleEvent = (sender: object, event: Event) => {
    const mouseMove = event as MouseEvent;
    console.log(`Mouse moved to ${mouseMove.x},${mouseMove.y}`);
}

This looks very similar to the first C# example — indeed, the syntax is almost identical. But if we take a look at the typescript playground, we can see that our code actually compiles to the following javascript:

    const mouseMove = event;

and the type assertion has disappeared completely. Type assertions always succeed. They’re a way for you to tell the typescript compiler “Suppress errors here, I know what I’m doing” which can be useful, but is more dangerous than you might expect. If you’ve cast to a type with more properties than the object actually has, then any later code will assume those properties exist — and you might see errors caused by impossible-looking undefined values.

Are type assertions completely unsafe? There is some minimal type checking for “unrelated” types. If the source type has nothing in common with the target type, then typescript will complain:

interface Cat {
    numLives: 9;
}

interface Dog {
    bark: () => void;
}

const animal = { bark: () => {} } as Cat
// ^ fails with an error: Conversion of type '{ bark: () => void; }' to type 'Cat' may be a mistake because neither type sufficiently overlaps with the other.

However, if we tried to compile { } as Cat , then typescript wouldn’t complain — because { } is a subset of the Cat interface.


So when should you use type assertions? I’d say the answer is as little as possible. Here are a couple of situations where they can be necessary:


So, to sum up: typescript type assertions may be more dangerous than you may think, and you should avoid them if possible. But sometimes they turn out to be necessary! The important thing, as always, is to be properly informed about how they work and to make deliberate decisions when writing code.

Original photo by Tom Claes on Unsplash