Home > Software engineering >  Are rust-style const generics in TypeScript?
Are rust-style const generics in TypeScript?

Time:01-01

In TypeScript, literals are types. Const-generics would allow me to use the value of the literal inside the type it is a part of, e.g.

class ImpreciseMoney<const MUL: number> {
    value: Number;

    constructor(value: number) {
        this.value = value;
    }

    normalize(): number {
        return this.value / MUL;
    }

    add<const RHS_MUL: number>(rhs: ImpreciseMoney<RHS_MUL>): ImpreciseMoney<MUL> {
        return new ImpreciseMoney<MUL>(this.normalize()   rhs.normalize());
    }
}

const JPY = 131.12;
const USD = 1;

let a = new ImpreciseMoney<JPY>(1000);
let b = new ImpreciseMoney<USD>(50);
let ab = a.add(b); // ImpreciseMoney<JPY> { value: 7555.75, };

Is this sort of thing possible in TypeScript?

Since this has been flagged for closure for not being specific, I guess I'll just describe what a const-generic is:

  • literals can be passed as type arguments
  • literals are also values
  • can I use that value as a constant within an item that takes generic parameters (which could be literals?

CodePudding user response:

During compilation, the TypeScript compiler strips away all typings and only emits the left-over JavaScript. This means that types can not be used to do anything at runtime.

Literal types are no exception to this rule. They may look like something the compiler could easily emit to the resulting JavaScript, but it would not align with the Design Goals of the language.

(enums can be seen as an exception of this rule since they are types with runtime representations)


Most of the times you can use appropriate runtime representations which hold the values of the type you want to use.

We can rewrite ImpreciseMoney, so that it takes a value of the generic type MUL as a parameter in the constructor. This runtime value can then be set to a property mul.

class ImpreciseMoney<MUL extends number> {
    value: number;
    mul: MUL

    constructor(mul: MUL, value: number) {
        this.value = value;
        this.mul = mul
    }

    normalize(): number {
        return this.value / this.mul;
    }

    add(rhs: ImpreciseMoney<number>): ImpreciseMoney<MUL> {
        return new ImpreciseMoney(this.mul, this.normalize()   rhs.normalize());
    }
}

This pretty much achieves your goals. You have the property mul which can be used at runtime for computations but also the generic type MUL which holds the literal type of whatever was set to mul.

The only difference for the caller is that the type argument was replaced with another non-type argument.

const JPY = 131.12;
const USD = 1;

let a = new ImpreciseMoney(JPY, 1000);
let b = new ImpreciseMoney(USD, 50);
let ab = a.add(b);

Playground

  • Related