Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What’s the difference between definite assignment assertion and ambient declaration?

When asserting that a field is definitely initialized in a class, what’s the difference between ! (exclamation point, definite assignment assertion) and the declare modifier?

The following code is an error in strict mode since TS doesn’t know for sure that the field has been initialized.

class Person {
    name: string; // Error: Property 'name' has no initializer and is not definitely assigned in the constructor.
}

I’ve seen 2 ways of handling this:

  1. Definite assignment assertion:
    class Person {
        name!: string;
    }
    
  2. Ambient declaration:
    class Person {
        declare name: string;
    }
    

I can’t see the difference between these two techniques. They both cure the error, they both don’t emit code, and they both don’t allow initializers. Does ambient declaration (released in v3.7) simply outdate definite assignment (released in v2.7)? Should declare be used instead of ! whenever possible?

like image 388
chharvey Avatar asked May 01 '21 22:05

chharvey


People also ask

What is definite assignment assertion?

The definite assignment assertion is a feature that allows a ! to be placed after instance property and variable declarations to relay to TypeScript that a variable is indeed assigned for all intents and purposes, even if TypeScript's analyses cannot detect so.

What is definite assignment C#?

Definite assignment is a rule simply stating that every variable must have a value before it's read from. The process of assigning a value to a variable for the first time is known as initialization. Once the initialization process has taken place, a variable is considered initialized.

Which of the following features are introduced in the latest version 2.7 of TypeScript?

TypeScript 2.7 introduces support for ECMAScript's numeric separators proposal. This feature allows users to place underscores ( _ ) in between digits to visually distinguish groups of digits (much like how commas and periods are often used to group numbers).


1 Answers

Declare is mainly useful for mocking values when playing around with the type system. In production code, it's rarely used.


declare name: string;

This says to the compiler:

"There is a property called name of type string. I shouldn't have to prove to you that name actually exists, but I want to use it anyway."

The declare keyword is typically used in type definition files that provide typings for files that Typescript cannot get type information from (such as plain JS files). So if I was reading your code, I would assume that name is getting monkey patched in from some JS file somewhere, and you are noting that here.

I would be incorrect.


name!: string;

This says to the compiler:

"There is a property called name with a type of string | undefined. It starts with a value of undefined. But every time I get or set that property, I want to treat it as type string."

Using this form it's clear to anyone reading the code that name is undefined at first, but is treated like a string anyway. That means it must be set in this file somewhere, just probably not in the constructor.

From what you are saying, I would be correct in those assumptions.


In practice the result is nearly identical. In both cases you have a string property that you never have to actually initialize. However, I would argue that the name!: string is far more clear about what is actually going on.

Also, declare never emits code. It makes something only exist in the type system. (Thanks for mentioning this @NoelDeMartin)

class Foo {
    bar!: string;
    declare baz: string;
}

let bar: string
declare let baz: string

Compiles to:

class Foo {
    constructor() {
        Object.defineProperty(this, "bar", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}
let bar;

Note that baz is completely absent from the output.


Lastly though, I have to mention, you should probably just refactor your code so that you can assign the property in the constructor. Both those methods are not as type safe since you could potentially treat an uninitialized value as a string, which will likely cause a crash if it happens.

like image 51
Alex Wayne Avatar answered Sep 28 '22 21:09

Alex Wayne