๐ŸŽ“ A1Learner
๐Ÿ”
0 of 24 lessons completed

Dart Sealed Classes

A sealed class is a class that restricts its subclasses to the same file. The compiler then knows every subclass and can check a switch for full coverage.

An exhaustive switch over shapes

Shape is sealed and its two subclasses live in the same file. The switch expression has one case per subclass and no wildcard, and each case binds the field of its subclass.

sealed class Shape {}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);
}

class Square extends Shape {
  final double side;
  Square(this.side);
}

String label(Shape shape) {
  return switch (shape) {
    Circle(radius: var r) => 'circle $r',
    Square(side: var s) => 'square $s',
  };
}

void main() {
  print(label(Circle(2.0)));
  print(label(Square(3.0)));
}

A missing subclass

This switch leaves out Triangle, one of the three subclasses. The compiler refuses the program and prints this error.

sealed class Shape {}

class Circle extends Shape {}

class Square extends Shape {}

class Triangle extends Shape {}

String label(Shape shape) {
  return switch (shape) {
    Circle() => 'circle',
    Square() => 'square',
  };
}

void main() {
  print(label(Circle()));
}

Three subclasses, three cases

Token is sealed with three subclasses that carry different fields. The switch covers all three, so each token prints with its own label.

sealed class Token {}

class Word extends Token {
  final String text;
  Word(this.text);
}

class Count extends Token {
  final int value;
  Count(this.value);
}

class Symbol extends Token {
  final String mark;
  Symbol(this.mark);
}

String describe(Token token) {
  return switch (token) {
    Word(text: var t) => 'word: $t',
    Count(value: var v) => 'count: $v',
    Symbol(mark: var m) => 'symbol: $m',
  };
}

void main() {
  print(describe(Word('dart')));
  print(describe(Count(3)));
  print(describe(Symbol('!')));
}

Example

sealed class Result {}

class Success extends Result {
  final String value;
  Success(this.value);
}

class Failure extends Result {
  final String message;
  Failure(this.message);
}

class Pending extends Result {}

String report(Result result) {
  return switch (result) {
    Success(value: var v) => 'success: $v',
    Failure(message: var m) => 'failure: $m',
    Pending() => 'pending',
  };
}

void main() {
  print(report(Success('saved')));
  print(report(Failure('no network')));
  print(report(Pending()));
}

Key points

  • The sealed keyword restricts subclassing to the file that declares the sealed class.
  • A switch over a sealed type is exhaustive when it has one case for every subclass.
  • An exhaustive switch expression needs no wildcard case.
  • Leaving out one subclass makes the program fail to compile, and the error names the missing subclass.
  • Subclasses of a sealed class can carry different fields, and each case binds the fields of its own subclass.

In this example, Result is a sealed class with three subclasses. The report function switches over a result with one case for each subclass, so no wildcard is needed. The three calls print a success line, a failure line, and a pending line.

Note

Sealed classes suit a fixed set of cases that one file can hold in full.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท DartPad

Exercise

Why can the compiler check a switch over a sealed class for full coverage?