Wasm-bindgen: Support for C-Style Enums

Created on 18 Feb 2018  路  9Comments  路  Source: rustwasm/wasm-bindgen

Supporting the full power of Rust enums is probably a lot of work, but C-Style enums seem more doable. Is this something worth while tackling? If so, I would volunteer to help out (although I might need a bit of coaching).

Most helpful comment

Supporting the full power of Rust enums is probably a lot of work

Shouldn't be as TypeScript's support for sum types is pretty good (in some aspects better than Rust's one as it doesn't require explicit tags and so can cover any combinations of types).

For example,

enum MyEnum {
    A,
    B(u32),
    C(u32, u32),
    D { x: u32, y: u32 },
}

could be translated to

type MyEnum =
  | { tag: 'A' }
  | { tag: 'B', value: number }
  | { tag: 'C', value: [number, number] }
  | { tag: 'D', value: { x: number, y: number }
  ;

All 9 comments

Supporting the full power of Rust enums is probably a lot of work

Shouldn't be as TypeScript's support for sum types is pretty good (in some aspects better than Rust's one as it doesn't require explicit tags and so can cover any combinations of types).

For example,

enum MyEnum {
    A,
    B(u32),
    C(u32, u32),
    D { x: u32, y: u32 },
}

could be translated to

type MyEnum =
  | { tag: 'A' }
  | { tag: 'B', value: number }
  | { tag: 'C', value: [number, number] }
  | { tag: 'D', value: { x: number, y: number }
  ;

Sounds great to me to support! I'd also be fine starting with C enums and moving from there to variants with payloads.

I'd also be more than willing to help out with questions! I'm currently traveling now so I may be a bit slower but I'll help when I can.

The bad news is that this support won't be trivial based on the current architecture but I don't think it'd be too hard to extend?

This is a great idea! IMO for signature parity, the translated enum should be:

type MyEnum =
  | { A: any }
  | { B: { '0': number } }
  | { C: { '0': number, '1': number } }
  | { D: { x: number, y: number } }
  ;

@dbkaplun That would make it much harder to match on it in JavaScript code as it doesn't support match statements like in Rust (yet). While with separate tag/value representation you'll be able to do:

function handleEnum(myEnum) {
  switch (myEnum.tag) {
    case 'A': ...; break;
    case 'B': ...; break;
    case 'C': ...; break;
    case 'D': ...; break;
  }
}

or

const myEnumHandlers = {
  A() { ... },
  B(value) { ... },
  C([x, y]) { ... },
  D({ x, y }) { ... }
};

function handleEnum(myEnum) {
  return myEnumHandlers[myEnum.tag](myEnum.value);
}

While it's possible to use something like Object.keys(myEnum)[0] to extract tag in your representation, it makes it more hacky and less readable IMO.

I thought about your question earlier and it would be better to have a consistent interface than to pander to switch. What about:

if (myEnum.A) {
} else if (myEnum.B) {
}
...

@alexcrichton can you weigh in?

At least in the Redux world this kind of enum (e.g., for things like Redux actions) are modeled in the tag/values approach. For example:

{type: "MyAction", foo: 1, bar: "baz"}

This works well and is easily modeled in plain JS or TypeScript. I think the other approach is fine, but I haven't seen it as much in the wild. Whatever the direction forward, I think it would be nice pick something familiar with JS devs.

I thought about your question earlier and it would be better to have a consistent interface

Consistent with what? There is no direct analogy to tagged enums in JS / TS either way. And { type, value } is actually at least consistent with how Rust enums are represented in memory.

Another reason for it would be that JS engines really don't like objects of different shapes (that is, different sets of properties) and would not be able to optimise any code using variables of such enum type.

Also, as pointed above, this is already quite common for JS APIs to use { type, ... } style for tagged enums in Redux, parsers (both ESTree AST for JavaScript and TypeScript's own AST use it) and many other domains where tagged unions are required, so it will be familiar to consumers and well optimised - win-win.

I haven't thought too too hard yet about enums with payloads yet unfortunately, but I think we'd probably want to basically "see what JS does" in terms of what most closely matches Rust and then do that.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

derekdreery picture derekdreery  路  3Comments

expobrain picture expobrain  路  4Comments

pauldorehill picture pauldorehill  路  3Comments

NateLing picture NateLing  路  3Comments

fitzgen picture fitzgen  路  3Comments