skip to content

In Dart, how can an enum implement interfaces and apply mixins, and how do you write generic code bounded by Enum?

level: seniorimportance: nice to knowfreq 22%

answer

  1. implements and with on the enum
  2. mixin fields must be final
  3. T extends Enum in generics
  4. no static values through T
  5. concrete classes cannot implement Enum

basics

~20 s

An enum can implement interfaces such as Comparable and apply mixins whose fields are final. Generic code uses T extends Enum to read index and name, but must receive the values list as an argument, since T.values does not compile.

solid answer

~40 s

Enums can declare `implements Comparable<OrderStatus>` or any interface, and `with SomeMixin`, as long as every instance field, including those a mixin adds, is `final`. That lets enums join ordinary polymorphism: a `List<Labeled>` can hold enum values alongside other implementers. `Enum` itself is the supertype of every enum, so `T extends Enum` lets a generic function use `index`, `name`, `Enum.compareByIndex` and the `EnumByName` extension on an `Iterable<T>`. Dart has no static members through type parameters, so `T.values` does not compile; pass `List<T> values` in, typically as `OrderStatus.values`. A concrete class cannot implement `Enum`, while an abstract class or interface may, which lets you declare a contract only enums can satisfy.

code

dart · 34 lines
dart
abstract interface class Labeled {
  String get label;
}

mixin Badge {
  String get label;
  String get badgeText => label.toUpperCase();
}

enum OrderStatus with Badge implements Labeled, Comparable<OrderStatus> {
  pending('Pending', 2),
  shipped('Shipped', 1),
  delivered('Delivered', 0);

  const OrderStatus(this.label, this.priority);

  @override
  final String label;
  final int priority;

  @override
  int compareTo(OrderStatus other) => priority - other.priority;
}

T parseOr<T extends Enum>(List<T> values, String? raw, T fallback) =>
    values.asNameMap()[raw] ?? fallback;

void main() {
  final List<Labeled> chips = OrderStatus.values;
  print(chips.map((c) => c.label).join(', ')); // Pending, Shipped, Delivered
  print([...OrderStatus.values]..sort()); // delivered, shipped, pending by priority
  print(OrderStatus.shipped.badgeText); // SHIPPED
  print(parseOr(OrderStatus.values, 'lost', OrderStatus.pending)); // OrderStatus.pending
}

go deeper

for a junior

Recall that enums can use implements and with, and that every enum is a subtype of Enum.

for a middle

Explain the final-field rule for mixins, how T extends Enum unlocks index, name and byName, and why T.values is unavailable.

for a senior

Design shared widgets and helpers around interfaces or Enum bounds so several enums reuse them without switches or casts.

for a principal

Decide which cross-cutting contracts belong on enums versus separate mapping layers, keeping shared packages decoupled from UI types.

## Enums are classes, so they join normal type hierarchies An enhanced enum is a class with a fixed set of instances, and it can take part in the type system like other classes, with two restrictions: it cannot `extend` anything, because it implicitly extends `Enum`, and every instance field must be `final`. ## Implementing interfaces - **`implements Comparable<OrderStatus>`** gives the enum a natural order that `list.sort()` uses without a comparator. A typical `compareTo` returns `index - other.index`, or compares a domain field such as a priority. - **Your own interfaces** work too. If `Labeled` declares `String get label`, both an `OrderStatus` enum and a `PaymentMethod` class can implement it, and a widget that shows a list of chips can accept `List<Labeled>` without knowing about either. The enum must provide every member of each interface, usually through its final fields or getters. ## Applying mixins An enum can use `with` to pick up behaviour from a mixin. The one extra rule: **fields added by a mixin must be final too**, because enum values are shared constants. Mixins that only add methods and getters, possibly relying on an abstract getter the enum implements, fit best: ```dart mixin Badge { String get label; String get badgeText => label.toUpperCase(); } ``` ## `Enum` as a supertype `Enum`, from `dart:core`, is implemented by every enum. That makes it useful as a **generic bound**: | With `T extends Enum` you can | Because | |---|---| | read `value.index` and `value.name` | `index` is on `Enum`; `name` is the `EnumName` extension on `Enum` | | call `values.byName(raw)` or `values.asNameMap()` on an `Iterable<T>` | `EnumByName` is an extension on iterables of enums | | sort with `Enum.compareByIndex` or `Enum.compareByName` | they are static generic comparators over `T extends Enum` | What you **cannot** do is reach the enum's static members through the type parameter. Dart has no static member access through type variables, so `T.values` is a compile-time error. Generic helpers therefore take the values list as a parameter: ```dart T parseOr<T extends Enum>(List<T> values, String? raw, T fallback) => values.asNameMap()[raw] ?? fallback; final status = parseOr(OrderStatus.values, json['status'] as String?, OrderStatus.pending); ``` ## Who may implement `Enum` `Enum` is declared as an `abstract interface class` with restrictions: 1. **Non-platform classes cannot extend or mix it in.** Only `enum` declarations get it as a superclass. 2. **Concrete classes cannot implement it.** A regular class that says `implements Enum` is an error. 3. **Abstract classes and interfaces may implement it.** `abstract interface class StatusLike implements Enum { ... }` declares a contract that only enums can satisfy, so a generic bound `T extends StatusLike` accepts enums that also carry your extra members. ## How this differs from a class hierarchy Implementing an interface does not make the enum open: its set of values is still closed, and no one can add a value by implementing the same interface. Other classes can implement `Labeled` alongside it, which is the point, but the enum itself stays a fixed set. And because `==` and `hashCode` cannot be overridden on an enum, values that implement an interface still compare by identity, which keeps them safe as map keys and set members. ## When to reach for these features - A shared chip, dropdown or filter widget that should accept several enums: bound it by an interface such as `Labeled`, or by `Enum` plus a label callback. - A generic parser or storage helper for any enum: `T extends Enum` with the values list passed in. - A natural ordering that differs from declaration order: `implements Comparable` with a `compareTo` over a domain field. ## Common mistakes - Writing `T.values` inside a generic function and expecting it to compile. - Adding a mutable field through a mixin and being surprised the enum is rejected. - Assuming a plain enum already implements `Comparable`; it does not unless you declare it.

  • Why does `T.values` not compile inside `void f<T extends Enum>()`?
    Dart does not allow static member access through a type parameter; a type variable is not a class reference. `values` is a static getter generated on each enum, so there is no way to reach it from `T`. Pass the list in, `f(OrderStatus.values)`, or pass a lookup function.
  • How can you declare a contract that only enums can implement?
    Declare an abstract class or interface that implements `Enum`, for example `abstract interface class StatusLike implements Enum { String get label; }`. Concrete classes cannot implement `Enum`, so only `enum` declarations can satisfy it, and a bound `T extends StatusLike` gets both `index`/`name` and your members.

saying these in an interview costs you the question

  • Enums cannot implement interfaces because they already extend Enum.
  • Inside a generic function you can call T.values when T extends Enum.
  • A regular class can implement Enum to behave like an enum.
  • Mixins applied to an enum may add mutable fields.
  • Every enum automatically implements Comparable by index.