{"id":13726792,"url":"https://github.com/Crell/enum-comparison","last_synced_at":"2025-05-07T22:30:32.429Z","repository":{"id":43456708,"uuid":"272480227","full_name":"Crell/enum-comparison","owner":"Crell","description":"A comparison of enumerations and similar features in different languages","archived":false,"fork":false,"pushed_at":"2021-02-22T22:19:05.000Z","size":257,"stargazers_count":83,"open_issues_count":0,"forks_count":8,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-04T06:03:19.371Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Crell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-15T15:47:57.000Z","updated_at":"2025-03-07T14:59:21.000Z","dependencies_parsed_at":"2022-07-07T23:29:09.726Z","dependency_job_id":null,"html_url":"https://github.com/Crell/enum-comparison","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2Fenum-comparison","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2Fenum-comparison/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2Fenum-comparison/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2Fenum-comparison/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Crell","download_url":"https://codeload.github.com/Crell/enum-comparison/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252965073,"owners_count":21832817,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:03:22.608Z","updated_at":"2025-05-07T22:30:32.098Z","avatar_url":"https://github.com/Crell.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# A survey of programming language enum support\n\n## Introduction\n\nAs of mid-2020, there is some discussion of adding enumerations (enums) to PHP.  There are many good reasons to do so, most around enabling better data modeling and type checking, but that doesn't suggest how to do it.  Enumerations in practice refer to a very wide range of functionality depending on the language, from barely above constants to a core part of the type system.\n\nAs I am wont to do, I decided the best thing to do would be to survey the existing marketplace and see what other languages did, and what we can steal outright.  (As the saying goes, \"PHP evolves by beating up other languages in dark alleys and going through their pockets for loose syntax.\")  I therefore looked at 12 different languages with some kind of native enumeration support.  The survey below is intended as a reasonably fair overview and summary of the available languages.  My own thoughts and analysis are included at the end.  For some languages I have included runnable sample code in the appropriate subdirectory.  Whether or not there is sample code depends primarily on whether I had a runtime for the language already installed.\n\nI deliberately excluded languages with no native enum support.  Languages such as Javascript, Go, or Ruby do not (as far as I can tell) have any native enumerations, although there are various hacky ways to simulate them in user space.  That is not of interest to us at this time.\n\nIf you spot any errors in the survey below, please let me know.\n\n## Survey\n\n- [C](#c)\n- [C++](#c-1)\n- [Java](#java)\n- [Python](#python)\n- [Typescript](#typescript)\n- [Haskell](#haskell)\n- [F#](#f)\n- [C#](#c-2)\n- [Swift](#swift)\n- [Rust](#rust)\n- [Kotlin](#kotlin)\n- [Scala](#scala)\n\n### C\n\nIn C, enumerations are really just a wrapper for named integer constants.  They are defined with the keyword `enum`, usually in combination with `typedef`.  For example:\n\n```c\n#include\u003cstdio.h\u003e\n\ntypedef enum { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } Day;\n\nvoid printer(Day d) {\n  printf(\"The day is: %d\\n\", d);\n}\n\nint main(void) {\n  Day d = Tuesday;\n\n  printer(d);\n  printer(4);\n   return 0;\n}\n```\n\nEven though `printer()` takes a `Day` parameter, passing an integer literal works fine.  If no integer value for an enum element is specified, the compiler assigns one automatically starting from 0.  So `Monday` is 0, `Tuesday` is 1, etc.  You can specify an equivalent integer for an enum value, including making multiple values refer to the same integer:\n\n```c\ntypedef enum {\n  Working,\n  Failed = 5,\n  Busted = 5;\n} Status;\n```\n\nNote that even though `d` is of `Day` type, the enum constant `Tuesday` is defined in the **global** scope.  That is, the following code does not compile, as `Monday` is defined twice.\n\n```c\ntypedef enum { Tuesday = 1, Monday, Wednesday } WeirdDays;\ntypedef enum { Monday, Tuesday, Wednesday } Day;\n```\n\n### C++\n\nC++ is backwards-compatible with C, so the [previous section](#c) applies.  In addition, starting with C++11, scoped enumerations (defined with `enum struct` or `enum class`) have been introduced.\n\nThe enums are defined the same way as in C (so individual enumerators' values can be specified, etc.).  There is no automatic conversion from the scoped enum type to the underlying integer type.\n\n*Note:* Even though the defining keywords are `enum struct`, the type itself does not behave like a `struct`: no fields or member methods can be defined.\n\n```cpp\n#include \u003ciostream\u003e\n\ntypedef enum { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } Day;\nenum struct ScopedDay { Monday = 9, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };\n\nvoid printer(Day d) {\n  std::cout \u003c\u003c \"The classical day is \" \u003c\u003c d \u003c\u003c '\\n';\n}\n\nvoid printer(ScopedDay d) {\n  std::cout \u003c\u003c \"The scoped day is \" \u003c\u003c static_cast\u003cint\u003e(d) \u003c\u003c '\\n';\n}\n\nint main() {\n  Day       d1 = Tuesday;\n  ScopedDay d2 = ScopedDay::Tuesday;\n\n  printer(d1); //  1\n  printer(d2); // 10\n  return 0;\n}\n```\n\n### Java\n\nIn Java, enums are, unsurprisingly, a shorthand for classes with class constants.  They can be defined standalone or within a class, since Java supports inner classes.  As a result, enums can support arbitrary methods.  The specific values can map to internal integer values, or they can be auto-assigned by the compiler.\n\nThe simple case looks like this:\n\n```java\nenum Suit {\n    HEARTS,\n    DIAMONDS,\n    CLUBS,\n    SPADES\n}\n```\n\nEnum values have a number of methods on them by default to access metadata, including `Suit.valueOf(\"HEARTS\")` (returns \"HEARTS\") and `Suit.valueOf(\"HEARTS\").ordinal()` (returns 0).\n\nThe values of an enum can be iterated as a set:\n\n```java\nfor (Suit s : Suit.values()){  \n    System.out.println(s);  \n} \n```\n\nBecause they're built on classes, enums can have methods.\n\n```java\nenum Suit {\n  HEARTS,\n  DIAMONDS,\n  CLUBS,\n  SPADES;\n\n  public String color() {\n    switch (this) {\n      case SPADES:\n        return \"Swords of a soldier\";\n      case CLUBS:\n        return \"Weapons of war\";\n      case DIAMONDS:\n        return \"Money for this art\";\n      default:\n        return \"Shape of my heart\";\n    }\n  }\n}\n```\n\nThe switch statement is not exhaustive on enums, however.\n\nEnum constructors are always private, so they can only be called from the definition of enum members.  They also support interfaces.\n\n```java\nenum Suit {\n    HEARTS(\"H\"),\n    DIAMONDS(\"D\"),\n    CLUBS(\"C\"),\n    SPADES(\"S\");\n\n    private String abbrev;\n\n    Suit(String abbrev) {\n        this.abbrev = abbrev;\n    }\n\n    String shortName() {\n        return abbrev;\n    }\n}\n```\n\nFurther reading: https://www.javatpoint.com/enum-in-java\n\n### Python\n\nPython builds its enum support on top of classes.  An \"enum class\" is simply a class that extends the `enum.Enum` parent, which has a lot of methods pre-implemented to provide Enum-ish behavior.  All properties of the class are enum members:\n\n```python\nimport enum\n\nclass Suit(enum.Enum):\n    HEARTS = enum.auto()\n    DIAMONDS = enum.auto()\n    CLUBS = 'C'\n    SPADES = \"S\"\n```\n\nEnum members can be any int or string primitive, or can be auto-generated.  The auto-generation logic can also be overridden by defining a `_generate_next_value_()` method in the class.  When an enum value is cast to a string, it always shows as `Card.CLUBS` or similar, but can be overridden by implementing the `__str__` method.\n\nEnum member names must be unique, but values need not be.  If two members have the same value then the syntactically first one wins, and all others are alises to it.  The aliases will be skipped when iterating an enum or casting it to a list.  If needed, you can get the original list with `Card.__members__.items()`.\n\nAs a class, an enum can also have methods.  However, the methods have no native way to vary depending on which enum value they're on.  You can check the value within the method, though:\n\n```python\nclass Suit(enum.Enum):\n    HEARTS = enum.auto()\n    DIAMONDS = enum.auto()\n    CLUBS = 'C'\n    SPADES = \"S\"\n\n    def color(self):\n        if self in [self.CLUBS, self.SPADES]:\n            return \"Black\"\n        else:\n            return \"Red\"\n```\n\nBecause Python lacks any meaningful type declarations on variables, parameters, or return values, there's no way to restrict a value to an enum list.  Enum classes also cannot be extended.\n\nThe `Enum` class also has an alternate function-style syntax for simple cases:\n\n```python\nSuit = Enum('Suit', 'HEARTS DIAMONDS CLUBS SPADES')\n```\n\nFurther reading: https://docs.python.org/3/library/enum.html\n\n### Typescript\n\nTypescript supports primitive enumerations, including both constant and runtime-defined values.  Depending on the details they may or may not get compiled away to literal constants in code.  It has its own dedicated keyword.\n\n```typescript\nenum Suit {\n    Hearts,\n    Diamonds,\n    Clubs,\n    Spades,\n}\n```\n\nis equivalent to\n\n```typescript\nenum Suit {\n    Hearts = 0,\n    Diamonds = 1,\n    Clubs = 2,\n    Spades = 3,\n}\n```\n\nEnums can also have string values if specified explicitly.  Values can be set based on some other value, even function definitions:\n\n```typescript\nenum FileAccess {\n    // constant members\n    None,\n    Read    = 1 \u003c\u003c 1,\n    Write   = 1 \u003c\u003c 2,\n    ReadWrite  = Read | Write,\n    // computed members\n    UserSetting = userDefaultValue()\n}\n```\n\nNormally enums exist at runtime, but a fully-constant enum can also be flagged to compile-away to raw constants in the source code:\n\n```typescript\nconst enum ShouldWe {\n    No,\n    Yes,\n}\n```\n\nEnum types can be used as type declarations:\n\n```typescript\nfunction pickCard(desiredSuit: Suit): Card { }\n```\n\nFurther reading: https://www.typescriptlang.org/docs/handbook/enums.html\n\n### Haskell\n\nStrictly speaking Haskell doesn't have enums, but the way its type system works gives you something close enough that I'm going to include it.  In Haskell, you define a new data type with the `data` keyword, which can be defined in terms of other data types and type constructors.\n\nIt's really hard to explain without going into the whole type system, so I'll stick to some examples:\n\n```haskell\ndata Suit = Hearts | Diamonds | Clubs | Spades\n```\n\nThe type \"Suit\" has only four values, one for each suit.  They are not backed by a primitive value but literally are those values only.  Haskell doesn't have methods as we'd understand them in the OOP world, and I've not been able to wrap my brain around Haskell enough to say if you can attach methods consistently to types of an Enum.  They can, however, be used in pattern matching:\n\n```haskell\ndata Color = Red | Black\n\nsuitColor :: Suit -\u003e Color\nsuitColor Hearts = Red\nsuitColor Diamonds = Red\nsuitColor _ = Black\n```\n\nBecause type values are technically not values but \"type constructors\" they can be parameterized by other values.  For instance, the infamous Maybe Monad is defined as:\n\n```haskell\ndata Maybe a = Just a | Nothing\n```\n\nThat is, a \"Maybe\" can be either the literal `Nothing` or a `Just` combined with some other value, which can then be extracted later using pattern matching.\n\n```haskell\nstuff :: Maybe a -\u003e Int\nstuff Nothing = 0\nstuff Just a = a\n```\n\nFurther reading: https://wiki.haskell.org/Type\n\n### F#\n\nF#, in what seems to be a very on-brand move, has both union types *and* enums.  They are very similar but not quite the same thing.\n\nUnion types in F# look and act an awful lot like Haskell, including the requirement that the unioned types start with a capital.\n\n```f#\ntype SuitUnion = Hearts | Diamonds | Clubs | Spades\n```\n\nThey have no underlying primitive equivalent.  F#'s `match` directive forces you to enumerate all possible values, to help avoid errors:\n\n```f#\nlet color = match x with \n    | Hearts -\u003e Red\n    | Diamonds -\u003e Red\n    | Clubs -\u003e Black\n    | Spades -\u003e Black\n```\n\nEnums in F#, by contrast, are backed by underlying integer primitives that you specify.  Strings are not allowed.  They can be all lowercase if you want, but have to be qualified when referencing to them:\n\n```f#\ntype SuitEnum = Hearts = 1 | diamonds = 2 | Clubs = 3 | Spades = 4\n\nlet color = match x with \n    | SuitEnum.Hearts -\u003e \"Red\"\n    | SuitEnum.diamonds -\u003e \"Red\"\n    | SuitEnum.Clubs -\u003e \"Black\"\n    | SuitEnum.Spades -\u003e \"Black\"\n    | _ -\u003e \"What kind of deck are you using?\"\n```\n\nEnums can be cast to and from integers.  That also, oddly, allow you to define an enum value that is out of range.\n\n```f#\n// This is, amazingly, legal.\nlet horseshoe = enum\u003cSuitEnum\u003e(5)\n```\n\nFor that reason, the `_` fallback match arm is required for enums, but not for unions.\n\nBecause F# doesn't have function parameter or return types, neither unions nor enums can be type defined in a function signature.\n\nFurther reading: https://fsharpforfunandprofit.com/posts/enum-types/\n\n### C#\n\nC# enums are explicitly just named integer constants, much like in C.  They can be defined within a class like constants, or (I think) stand-alone with a namespace.\n\n```csharp\nenum Suits \n{\n    Hearts = 0,\n    Diamonds,\n    Clubs,\n    Spades\n}\n```\n\nIf a value is not specified, it will be set to the highest existing value + 1.  0 is the default first value but you can set your own.  They are referenced scoped, so `Suits.Diamonds`, `Suits.Spades`, etc.\n\nValues can also be defined based on other enum values, bitmask style, such as `RedCards = Hearts|Diamonds`.  However, that only works if the explicit values are defined as bit flags.\n\nEnums need to be cast to an integer explicitly in order to use as an int.\n\n```csharp\nConsole.WriteLine((int)WeekDays.Monday);\n```\n\nAn `Enum` class contains various static methods for manipulating enumerations further.  For instance, to get a list of the names in a given enumeration:\n\n```csharp\nforeach (string str in Enum.GetNames(typeof(WeekDays))) {\n    Console.WriteLine(str);\n}\n```\n\nOr this somewhat crazy way to cast an integer up to an enum member:\n\n```csharp\nWeekDays wdEnum;\nEnum.TryParse\u003cWeekDays\u003e(\"1\", out wdEnum);\nConsole.WriteLine(wdEnum);\n```\n\nAlthough they're not a class, you can technically add \"extension methods\" to enums that end up looking kind of like them.  For instance:\n\n```csharp\npublic static string Color(this Suit s) {\n    switch (s)\n    {\n        case Hearts: return \"Red\";\n        case Diamonds: return \"Red\";\n        case Clubs: return \"Black\";\n        case Spades: return \"Black\";\n    }\n}\n\nvar theColor = Suit.Clubs.Color();\n```\n\nFurther reading: https://www.tutorialsteacher.com/csharp/csharp-enum\n\n### Swift\n\nSwift's enumerations are closer to union types, but still called enumerations.  (Go figure.)  They form a full fledged type with limited legal values.  That means the type has to be capitalized, and the values not.\n\n```swift\nenum Suit {\n    case hearts\n    case diamonds\n    case clubs\n    case spades\n}\n// or\nenum Suit {\n    case hearts, diamonds, clubs, spades\n}\n```\n\nOnce defined, values can be defined of that type, and Swift's type inference capability can shorten the syntax somewhat.\n\n```swift\nvar card = Suit.clubs\n\n// since card is now bound to the type Suit, you can now do this:\ncard = .spades\n```\n\nYou can match on an enum value with `switch`, and it must either be exhaustive or have a default:\n\n```swift\nswitch card {\n    case .spades:\n        print(\"The swords of a soldier.\")\n    case .clubs:\n        print(\"Weapons of war.\")\n    case .diamonds:\n        print(\"Money for this art.\")\n    default:\n        print(\"That's not the shape of my heart.\")\n}\n```\n\nEnums are not natively iterable, but they can be converted into that easily:\n\n```swift\nenum Suit: CaseIterable {\n    case hearts, diamonds, clubs, spades\n}\n\nfor s in Suit.allCases {\n    print(s)\n}\n```\n\nSwift allows enums to have what it calls \"associated values,\" creating what is variously called a \"discriminated union\" or \"tagged union\" depending on whom you ask.  Each value can have its own set of associated values that could be the same or different.\n\n```swift\ncase Suit {\n    case hearts(String)\n    case diamonds(String)\n    case clubs(String)\n    case spades(String)\n}\n\nvar threeOfDiamonds = Suit.diamond(\"3\")\n```\n\nEach instance of an associated value enum is then not equal to another, even if they're of the same enum value.  Seemingly the only way to get those values back out, though, is with pattern matching:\n\n```swift\nswitch card {\n    case .spades(let value):\n        print(\"The \\(value) of Spades\")\n    case .clubs(let value):\n        print(\"The \\(value) of Clubs\")\n    case let .diamonds(value):\n        print(\"The \\(value) of Diamonds\")\n    case let .hearts(value):\n        print(\"The \\(value) of Hearts\")\n}\n```\n\nFor one-off cases, you can use `if let`.\n\n```swift\nif case let .clubs(val) = card {\n    print (\"The \\(val) of Clubs\")\n}\n```\n\n(Those all do the same thing, but digging into the intricacies of Swift's pattern matching is out of scope for now.)\n\nEnums can *also* support \"raw values,\" if specified explicitly, but they must be of the same primitive type:\n\n```swift\nenum Suit: Character {\n    case hearts = \"H\"\n    case diamonds = \"D\"\n    case clubs = \"C\"\n    case spades = \"S\"\n}\n```\n\nIf you list only one raw value, Swift will try to generate a raw value for the rest based on the type used.  It's also possible to initialize an enum case from a raw value, if one was defined:\n\n```swift\nlet card = Suit(rawValue: \"B\")\n```\n\nThis actually creates an \"optional\" of type `Suit?`, meaning it may or may not be legal and you have to explicitly check it.  (Optionals are essentially a syntactic Maybe Monad, and way off topic.)\n\nYou can even define an enumeration in terms of itself, which is just all kinds of weird.  From the documentation:\n\n```swift\n    indirect enum ArithmeticExpression {\n        case number(Int)\n        case addition(ArithmeticExpression, ArithmeticExpression)\n        case multiplication(ArithmeticExpression, ArithmeticExpression)\n    }\n```\n\nAnd they go further by supporting methods on enumerations of all of the above types, the body of which would meaningfully have to be a switch:\n\n```swift\ncase Suit {\n    case hearts(String)\n    case diamonds(String)\n    case clubs(String)\n    case spades(String)\n\n    func color: String {\n        switch self {\n            case .hearts: return \"Red\"\n            case .diamonds: return \"Red\"\n            case .clubs: return \"Black\"\n            case .spades: return \"Black\"\n        }\n    }\n}\n\nprint(Suit.clubs(\"3\").color());\n// Prints \"Black\"\n```\n\nFurther reading: https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html\n\n### Rust\n\nAs Rust's main syntactic goal seems to have been \"Haskell, but with lots of curly braces,\" the language supports enumerations with and without associated values, either positional or named.  \n\nAll of the following are legal:\n\n```rust\n// The values themselves.\nenum Suit {\n    Hearts,\n    Diamonds,\n    Clubs,\n    Spades,\n}\n\n// With one or more tuple associated values.\nenum Card {\n    Hearts(i8),\n    Diamonds(i8),\n    Clubs(i8),\n    Spades(i8),\n}\n\n// With one or more struct-associated values.\nenum Card {\n    Hearts{val: i8},\n    Diamonds{val: i8},\n    Clubs{val: i8},\n    Spades{val: i8},\n}\n\n// With an integer (only) explicit value.\nenum Suit {\n    Hearts = 3,\n    Diamonds = 4,\n    Clubs = 5,\n    Spades = 6,\n}\n```\n\nEnum values can be referenced scoped from their type, `Suit::Heart`, or first imported with `use Suit::*` and then used unqualified.  Because they're a full type, they can be used in function signatures.\n\nEnums are almost always used with either `match` or `if let`, the latter of which being a sort of inverted way to care about only a single branch of a match.  The `match` version must be exhaustive or have a default.\n\n```rust\nlet msg = match card {\n    Suit::Spades =\u003e \"Swords of a soldier\".to_string(),\n    Suit::Clubs =\u003e \"Weapons of war\".to_string(),\n    Suit::Diamonds =\u003e \"Money for this art\".to_string(),\n    _ =\u003e \"Shape of my heart\".to_string(),\n};\n\nif let Diamonds(val) = card {\n    println!(\"{} diamonds are a girl's best friend\", val)\n}\n```\n\nThe only way to extract associated values out of the enum is with pattern matching, which in Rust is almost absurdly robust:\n\n```rust\nuse Card::*;\nlet the_val = match Card {\n    Clubs(x) | Hearts(x) | Spades(x) | Diamonds(x) =\u003e x\n};\n```\n\nAs it's a full type, it can also have methods.  Or in Rust-speak, \"implementations,\" including of traits (what most languages would call an interface).  They can do pretty much everything a `struct` can.  Their body will in most cases be just a bit `match`.\n\n```rust\nimpl Suit {\n    fn color(\u0026self) -\u003e String {\n        match self {\n            Self::Hearts =\u003e \"Red\".to_string(),\n            Self::Diamonds =\u003e \"Red\".to_string(),\n            Self::Clubs =\u003e \"Black\".to_string(),\n            Self::Spades =\u003e \"Black\".to_string(),\n        }\n    }\n}\n```\n\n(The capitalized `Self` in this case is an implicit alias to `Suit`.)\n\nFurther reading: https://doc.rust-lang.org/rust-by-example/custom_types/enum.html\n\n### Kotlin\n\nKotlin also has not one but two enum-esque systems: Enums and Sealed Classes.  The difference between them is subtle and confusing.\n\nEnums are a class that inherits from an Enum class implicitly.\n\n```kotlin\nenum class Suit {\n    HEARTS,\n    DIAMONDS,\n    CLUBS,\n    SPADES\n}\n```\n\nEach enum value is technically a \"constant object.\"  By default they're bare, but can also take int or string values constructor-style.\n\n```kotlin\nenum class Suit(val abbrev: String) {\n    HEARTS(\"H\"),\n    DIAMONDS(\"D\"),\n    CLUBS(\"C\"),\n    SPADES(\"S\")\n}\n```\n\nEnums in Kotlin support methods, and unlike most languages here the methods may be defined separately for each value.  Technically they're all implemented as subclasses, with the parent as an abstract base class.  An enum can even support interfaces.\n\n```kotlin\ninterface Colorable {\n    fun color()\n}\n\nenum class Suit(val abbrev: String) {\n    HEARTS(\"H\") {\n        override fun color(): String = \"Red\"\n    },\n    DIAMONDS(\"D\") {\n        override fun color(): String = \"Red\"\n    },\n    CLUBS(\"C\") {\n        override fun color(): String = \"Black\"\n    },\n    SPADES(\"S\") {\n        override fun color(): String = \"Black\"\n    };\n\n    abstract fun color(): String\n\n    fun abbreviation(): String {\n        return this.abbrev\n    }\n}\n```\n\nEnums have a number of built-in methods and properties, which make it possible to iterate an enum or get its value.  That also makes them trivially serializable, unlike Sealed Classes.\n\nSealed Classes, meanwhile, are almost like normal classes except that the list of subclasses is fixed at compile time and they must appear in the same source file.\n\nWhereas Enums are singletons, sealed classes may be singleton or instance-based.\n\n```kotlin\nsealed class Action\n\n// This is a singleton sealed class\nobject Quit: Action()\n\n// This is an instance-able sealed class\nclass Move(val dir: String): Action()   \n```\n\nSince they're objects/methods in their own right, they can have whatever methods you want, inherited or not.  However, they do not have the automatic methods or properties of Enums that make them serializable.\n\nKotlin supports a `when` syntax as an alternative to `switch` that is an expression, and can, in some cases, detect exhaustiveness.\n\n```kotlin\nvar result = when (card) {\n    Suit.SPADES -\u003e \"The swords of a soldier\"\n    Suit.CLUBS -\u003e \"Weapons of war\"\n    Suit.DIAMONDS -\u003e \"Money for this art\"\n    else -\u003e \"The shape of my heart\"\n}\n```\n\nFurther reading: https://blog.kotlin-academy.com/enum-vs-sealed-class-which-one-to-choose-dc92ce7a4df5\n\n### Scala\n\nScala enums are also built on objects.\n\n```scala\npackage com.crell.poker {\n    object Suit extends Enumeration {\n        type Suit = Value\n        val HEARTS, DIAMONDS, CLUBS, SPADES = Value\n    }\n}\n\n// ...\nobject Main extends App {\n    import com.crell.poker.Suit._\n\n    var s = CLUBS\n\n    // Iteration\n    Suit.values foreach println\n}\n```\n\nThey can carry values, including multiple values, which must be pre-set and not vary by instance.  They also can support methods that way, although my Scala-fu is not strong enough to know if my syntax here is entirely correct. :-)\n\n```scala\nobject Suit extends Enumeration {\n    protected case class Val(abbrev: String) extends super.Val {\n        def color: String = abbrev.match {\n            case Suit.HEARTS =\u003e \"Red\"\n            case Suit.DIAMONDS =\u003e \"Red\"\n            case Suit.CLUBS =\u003e \"Black\"\n            case Suit.SPADES =\u003e \"Black\"\n        }\n    }\n    type Suit = Value\n    val HEARTS = Val(\"H\")\n    val DIAMONDS = Val(\"D\")\n    val CLUBS = Val(\"C\")\n    val SPADES = Val(\"S\")\n}\n```\n\nFurther reading: https://www.scala-lang.org/api/current/scala/Enumeration.html\n\n## Summary\n\nFolded into a convenient table, a feature summary looks like this:\n\n| Language          | C/C++  | Java | Python | Typescript | Haskell | F# (Union) | F# (Enum) |  C#  | Swift | Rust | Kotlin (Enums) | Kotlin (Sealed) | Scala\n|-------------------|--------|------|--------|------------|---------|------------|-----------|------|-------|------|----------------|-----------------|-------\n| Unit values       | No     | No   | No     | No         | Yes     | Yes        | No        | No   | Yes   | Yes  | Yes            | Ish?            | Yes\n| Int values        | Yes    | Yes  | Yes    | Yes        | No      | No         | Yes       | Yes  | Yes   | Yes  | Yes            | Ish?            | Yes\n| String values     | No     | Yes  | Yes    | Yes        | No      | No         | No        | No   | Yes   | No   | Yes            | Ish?            | Yes\n| Associated values | No     | No   | No     | No         | Yes     | No         | No        | No   | Yes   | Yes  | No             | Yes             | No\n| Methods           | No     | Yes  | Yes    | No         | No?     | No         | No        | Ish  | Yes   | Yes  | Yes            | Yes             | Yes\n| Type checked      | Ish    | Yes  | No     | Yes        | Yes     | No         | Ish       | Yes  | Yes   | Yes  | Yes            | Yes             | Yes?\n| Iterable          | No     | Yes  | Yes    | No         | No      | No         | No        | Yes  | Yes   | No   | Yes            | No              | No\n\nIn terms of overall capability, Swift appears to have the edge with Rust a very close second.  However, Rust also seems to have more powerful associated values ability (tuples or structs), and the usefulness of iterating enum types is debatable.  I'm going to call it a qualified tie between those two in raw expressive power.\n\n## Analysis\n\nBroadly speaking, I would separate the languages into a few categories:\n\n* **Fancy Constants**: C, Typescript, F#\n* **Fancy Objects**: Python, Java, C#, Scala\n* **Algebraic Data Types**: Haskell, Swift, Rust, Kotlin\n\nWhile they are superficially similar, and often use the same terminology, they approach the problem from different ways.  The Fancy Constants languages are offering a syntactic convenience, but little else.  Often they get compiled away at runtime, and their type checking may be incomplete.\n\nThe Fancy Objects languages take that a step further and offer methods on enum types, which offers a centralized place to put a switch, match, or whatever branching syntax for RTTI.  That is helpful, and helps with data modeling in ways that Fancy Constants do not.  If the methods need to vary by enum type more than just a little, though, you run into some contortions and may find yourself better off with normal objects and interfaces.\n\nThe main differentiator for ADT languages, as I'm using them here, is that they can be parameterized with different values.  That offers another layer again of potential functionality and data modeling.  It also becomes a natural and easy way to implement Monads in user space, and Haskell, Swift, and Rust all do exactly that in their core libraries, particularly for Maybe/Optional.  That makes them an extremely robust way to handle data modeling in your application, and to \"make invalid states unrepresentable,\" which is an excellent feature if you can get it.\n\nThe downside is that once you start parameterizing enum values, you no longer get a guarantee that a Club is a Club is a Club.  They may well be two different Clubs.  The implementation details here around equality (a tricky subject in the best of circumstances) are the devil's hiding place.  The other catch is that, as far as I can tell, no language with parameterized enum values lets you get at them easily without doing pattern matching.  Depending on your use case that may be no big deal or may be a deal-breaker.  In practice, I think it largely comes down to how easy the syntax is for pattern matching; Of all things I'd say Haskell is the nicest here, followed by Swift, then Rust.  (Or possibly Rust then Swift, depending on your tastes.  Rust gets very tricky when you have struct-parameterized enums.)\n\n\nhttps://twitter.com/Tojiro/status/823286025535393792\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrell%2Fenum-comparison","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCrell%2Fenum-comparison","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrell%2Fenum-comparison/lists"}