Skip to content

Support enums #13

Closed
Closed

Description

@aapoalas

A much harder bit but potentially very "lucrative".

A "normal" enum would be derived SoAble somehow like this:

#[repr(u8)]
#[derive(SoAble)]
enum Value<'a> {
  Undefined = 1,
  Null,
  Boolean(bool),
  String(StringIndex<'a>),
  SmallString(SmallString),
}

union ValueField0<'a> {
  undefined: (),
  null: (),
  boolean: bool,
  string: StringIndex<'a>,
  small_string: SmallString,
}

struct ValueRef<'soa, 'a> {
  discriminant: &'soa Discriminant<Value>, // this is an actual type in Rust: https://doc.rust-lang.org/core/mem/struct.Discriminant.html
  _0: &'soa ValueField<'a>
}

ie. We create as many _N unions and enums as there are variants, with each variant getting its own union field. Note that this is a bit dangerous (and not just because of the unions) as a user might have a single variant which as many, many fields and we'd then generate a whole bunch of fields for that. That's kind of "user error" at that point though: your enum should be reasonably SoAble to make the cut.

Note also that we do use &'soa Discriminant<Value> instead of reading the Discriminant out; while it'd be pretty cool to automatically create an enum ValueRef which reuses the discriminants of Value and just contains a reference in each field, it would be impossible to make that work for ValueSlice and furthermore sometimes you don't actually care to read the discriminant. See below.

A "named" enum is an interesting example as well:

struct TokenIndex(u32);
struct NodeIndex(u32);
struct ExtraIndex(u32);
struct OptionalTokenIndex(Option<NonNullU32>);
struct OptionalNodeIndex(Option<NonNullU32>);
struct OptionalTokenAndNode {
  token: OptionalTokenIndex,
  node: NodeIndex,
}
struct ExtraAndOptionalNode {
  extra: ExtraIndex,
  node: OptionalNodeIndex,
}

#[derive(SoAble)]
enum Node {
  Root {
    main_token: TokenIndex,
  },
  TestDecl {
    main_token: TokenIndex,
    data: OptionalTokenAndNode,
  },
  GlobalVarDecl {
    main_token: TokenIndex,
    data: ExtraAndOptionalNode,
  }
}

So this would be a collection of enum variants where each variant has at least main_token (1-byte) and then possibly a data field (2x 4-byte). This would derive SoAble like this:

union NodeData {
  root: TokenIndex,
  test_decl: OptionalTokenAndNode,
  global_var_decl: ExtraAndOptionalNode,
}

struct NodeRef<'soa> {
  discriminant: &'soa Discriminant<Node>,
  main_token: &'soa TokenIndex, // note: not behind a union because all fields agree on the type!
  data: &'soa NodeData,
}

This Node example is the first few lines of Zig's AST which is pretty great, but in the SoAble derive format it gets kind of even better, or at least it has the potential to become really great. Maybe it would be better to copy the discriminants into a separate NodeDiscriminant type so that the variants could be named? Or maybe not, time will tell.

One thing that is sure is that it'll be pretty easy to write safe APIs on top of this, as access into noderef.data union's fields will be fairly self-explanatory: if you match on discriminant == GlobalVarDecl then you'll access noderef.data.global_var_decl no problem.

Activity

  1. aapoalas commented on Sep 8, 2025

    @aapoalas
    MemberAuthor

    Few implementation notes on this:

    1. Allow enums with tuple-like variant payloads to have varying amounts of tuple fields. Split up each tuple field into its own SoA tuple representation field by index.
    2. Allow enums with struct-like variant payloads to omit some fields. Split up each struct field into its own SoA tuple representation field by name.
    3. Do not allow enums to mix tuple-like and struct-like variant payloads. Refuse to derive SoAble for an enum with both A(u32) and B { data: u32 }.
    4. If a field (tuple or struct) is present in every variant and has the same type in every variant, use that type as the SoA tuple representation field's type directly.
    5. If a field (tuple or struct) is present only in some variants or has varying types, generate a union and use that as the SoA tuple representation field's type. Name each union field according to the discriminant name and fill in any missing variants with ().
  2. aapoalas commented on Nov 17, 2025

    @aapoalas
    MemberAuthor

    Done via #41

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions