Is there a way to implement Rust's enum variant classes or Kotlin sealed classes in TypeScript?

When working with HTTP responses in TypeScript, I am interested in creating a variant type that can represent different states. In Rust, a similar type can be defined as:

enum Response<T> {
    Empty,
    Loading,
    Failure(String),
    Success(data: T),
}

In Kotlin, sealed classes can achieve the same concept:

sealed class Response<out: T> {
  object Empty: Response<Nothing>()
  object Loading: Response<Nothing>()
  class Failure(val err: string): Response<Nothing>()
  class Success(val data: T): Response<T>
}

I wonder if there is a way to implement this variant type structure in TypeScript?

Answer №1

Special thanks to @gabriele-petrioli for suggesting the use of a discriminated union.

type EmptyState = {
  kind: "empty";
};

type LoadingState = {
  kind: "loading"
};

type ErrorState = {
  kind: "error"
  message: string;
};

type SuccessState<T> = {
  kind: "success"
  data: T;
};

type RequestState<T> =
  | EmptyState
  | LoadingState
  | ErrorState
  | SuccessState<T>;

function process<T>(response: RequestState<T>) {
  switch (response.kind) {
    case "error":
      response.message;
      break;
    case "success":
      response.data;
  }
}

Answer №2

Below is a versatile type that automatically builds the union.

Additionally, I've included a convenient intoEnum function for simplified usage.

type PartialRustEnum<K extends string, V> = {
    kind: K
} & {
    [P in K]: V
}

export type RustEnum<T> = {
    [K in keyof T & string]: PartialRustEnum<K, T[K]>;
}[keyof T & string]

export function intoEnum<K extends string, V>(kind: K, value: V): PartialRustEnum<K, V> {
    return { kind, [kind]: value } as any
}

Example of usage:

type FooOrBar = RustEnum<{
    foo: number
    bar: string
}>

let a: FooOrBar = intoEnum('foo',32)

if(a.kind === 'foo'){
  // recognized as foo
  console.log(a.foo)
}else{
  // recognized as bar
  console.log(a.bar)
}



Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What steps should I take to ensure my paths in Ionic 4 Typescript are functioning properly?

Currently, I'm in the process of transitioning my Ionic 3 application to Ionic 4 with Typescript 3.1, but I'm facing challenges with the Typescript paths. Within my tsconfig.json, I have the following setup: "paths": { "@models": [ "src/mod ...

Understanding the Purpose of the Pipe Function in Angular 2 and Typescript Observables

Recently, I encountered a situation where I needed to accept an Observer parameter in a function written in Typescript. I struggled to find a solution, reminding me of working with a delegate parameter in C#. The specific scenario involved adding a bookend ...

The updated values in an Angular application may not always be accurately represented by interpolated values

The values of the elements in the dropzone1 array only show the initial top and left values, not the latest ones. Within the draw() function, I add the top and left values to the topLeft array and then push it to the dropzone1 array inside the move() func ...

Tips for showcasing styled text in Vue using API data

I'm having trouble formatting text in Vue. Within a component, I have a textarea that stores a string with backspaces, etc ... in an API like this: A cellar but not only...\n\nWelcome to the Nature & Wine cellar, a true Ali-baba's cave ...

I am looking to develop a unique event that can be triggered by any component and listened to by any other component within my Angular 7 application

Looking to create a unique event that can be triggered from any component and listened to by any other component within my Angular 7 app. Imagine having one component with a button that, when clicked, triggers the custom event along with some data. Then, ...

Having trouble retrieving spot price using Uniswap SDK due to a transaction error LOK

const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle( immutables.token0, immutables.token1, immutables.fee, amountIn, 0 ) I set up a pool on Uniswap V3 for two ERC20 dummy tokens by using the createPool() met ...

The dropdown cannot be disabled because it is being passed as an input parameter

We are currently utilizing PrimeNG along with Angular 15. Scenarios: According to the requirements, we need the ability to disable the PrimeNG dropdown control based on a selection. Problem: The disabled property of <p.dropdown> is not functioning ...

Eliminate private TypeScript class properties from an object by using Vue 3's ref and reactive features

When an object with private properties or methods is passed to ref() or reactive(), the resulting proxy no longer contains the private properties. Consider the following class: class Pirate { name: string; age: number; private dream : st ...

From milliseconds to hours: a straightforward conversion

Given a start date, time and end date, time, I am trying to calculate the total travel duration. The output is in milliseconds and needs to be converted into hours format. Despite attempting some solutions shared here, I haven't been successful. < ...

What is the best method for combining two observables into one?

My goal is to initialize a list with 12 users using the URL ${this.url}/users?offset=${offset}&limit=12. As users scroll, the offset should increase by 8. I plan to implement infinite scrolling for this purpose. However, I am facing an issue with appen ...

Adding a total property at the row level in JavaScript

Here is a JavaScript array that I need help with: [{ Year:2000, Jan:1, Feb: }, {Year:2001, Jan:-1, Feb:0.34 }] I want to calculate the total of Jan and Feb for each entry in the existing array and add it as a new property. For example: [{ Year:2000, Ja ...

When there is data present in tsconfig.json, Visual Studio Code does not display errors inline for TypeScript

After creating an empty .tsconfig file (consisting solely of "{ }"), Visual Studio Code immediately displays errors both inline and in the "problems" section. Interestingly, when I populate the tsconfig.json file with data, these errors disappear. Is there ...

Is it possible to execute methods in the initialization phase using Express Route?

My express app has a simple structure, with the main file app.ts configured like this import * as express from "express"; const application: express.Application = express(); application.get("/", function( request: express.Request, ...

Creating a modal dialog using a function in a TypeScript file

Hey there, fellow developers! I have a question that might seem simple. So, in my HTML code I've got a Modal Dialog that pops up using ng2 Bootstrap. It's working fine, but... I want to replace this line of code "<button class="btn btn-prim ...

In the world of mathematics, the equation 1+1 may actually equal 11 instead

I have a TypeScript class where there are no import statements at the top. The issue I am facing is that when I use calculateDate() and run the addMonth(new Date(), 1) function, it ends up adding 11 months to today instead of just 2. Upon investigation, ...

An error message occurs in TypeScript when trying to access a property that does not exist in an array

I'm having trouble figuring out this issue... I am receiving data from an API and storing it as an array. I'm attempting to access the data in this format... this.data.results[i].datas[j].dataType However, I keep getting the error "property res ...

Provide a string argument when instantiating an abstract class

I am searching for a method to assign a name string within a class and utilize it in the abstract class at the constructor level, without the need for a function. Opening up the constructor is not an option due to using typedi. You can access the playgrou ...

"Organize your files with React and TypeScript using a file list

interface IVideos { lastModified: number, name: string, path: string, size: number, type: string, webkitRelativePath: string } const [videos, setVideos] = useState<IVideos[] | null>([]); <input type="file" onChange={(event) => ...

The existence of useRef.current is conditional upon its scope, and it may be null in certain

I'm currently working on drawing an image on a canvas using React and Fabric.js. Check out the demo here. In the provided demo, when you click the "Draw image" button, you may notice that the image is not immediately drawn on the canvas as expected. ...

Determining in Angular 8 whether a value has been altered by a user or by a method call

Within my select element, the value is currently being assigned through an ngOnInit call. Here is an example of the HTML code: <select name="duration" [(ngModel)]="exercisePlan.duration" (ngModelChange)="onChange($event)"> <option *ngFor="l ...