Skip to main content
An extension type is a zero-cost, compile-time abstraction that wraps an existing type (the representation type) with a distinct static interface. It enforces strict type separation during static analysis but is erased at runtime, meaning the extension type and its representation type are identical in the compiled binary.

Syntax Declaration

An extension type is declared using the extension type keywords, followed by the name, the primary constructor defining the representation object, and the body.
// Declaration
// 'int' is the representation type.
// 'i' is the representation object.
extension type E(int i) {
  // Member definition
  void show() {
    print('Value is $i');
  }
}

void main() {
  // Instantiation
  E myObject = E(10);
  
  // Member access
  myObject.show(); 
}

Representation and Type Erasure

The core characteristic of an extension type is type erasure. While the compiler treats the extension type E and the representation type int as distinct, they are the same object at runtime.
  • Static Analysis: E is not a subtype of int (unless specified via implements). An int cannot be assigned directly to a variable of type E without using the constructor.
  • Runtime: The type E is erased. The runtime type of an instance of E is int.
extension type Wrapper(int value) {}

void main() {
  var w = Wrapper(42);
  
  // Static check: true
  // The variable 'w' is statically typed as Wrapper
  
  // Runtime check: true
  // The underlying object is strictly an int
  print(w is int); 
  
  // Runtime check: true
  // Because 'Wrapper' erases to 'int', this check passes for any int
  print(w is Wrapper); 
}

Member Access and Encapsulation

Extension types restrict access to the members of the representation type but expose the representation object itself via a getter derived from the primary constructor.
  1. Representation Object Access: The primary constructor parameter (e.g., id) automatically induces a getter with the same name on the extension type. To fully encapsulate the representation object, the parameter name must be private (prefixed with _).
  2. Underlying Member Hiding: Methods and properties of the representation type (e.g., int.isEven) are not exposed unless explicitly redeclared or exposed via implements.
// Case 1: Public representation object
extension type PublicId(int id) {
  bool get isValid => id > 0;
}

// Case 2: Private representation object
extension type PrivateId(int _id) {
  bool get isValid => _id > 0;
}

void main() {
  var pub = PublicId(10);
  print(pub.id);      // Allowed: The getter 'id' is public
  // pub.isEven;      // Error: 'isEven' is not defined for 'PublicId'

  var priv = PrivateId(20);
  // print(priv._id); // Error: The getter '_id' is private (inaccessible outside library)
  print(priv.isValid); // Allowed
}

Type Hierarchy and implements

Extension types support a form of inheritance using the implements keyword. This clause allows the extension type to be a subtype of other types, exposing their members.

Implementing Non-Extension Types

An extension type can implement its representation type or any supertype of its representation type. This makes the extension type a subtype of the implemented type and exposes its members.
// Valid: implements the representation type (int)
extension type TransparentInt(int value) implements int {}

// Valid: implements a supertype of the representation type (num is a supertype of int)
extension type NumericId(int value) implements num {}

void main() {
  var t = TransparentInt(5);
  print(t.isEven); // Allowed: inherited from int
  
  var n = NumericId(10);
  print(n.abs());  // Allowed: inherited from num
  // n.isEven;     // Error: isEven is defined on int, not num
}

Implementing Other Extension Types

An extension type Sub can implement another extension type Super if the representation type of Sub is a subtype (or the same type) of the representation type of Super.
extension type Super(num value) {
  void describe() => print('Number: $value');
}

// Valid: 'int' (Sub's rep) is a subtype of 'num' (Super's rep)
extension type Sub(int value) implements Super {
  void specific() => print('Integer: $value');
}

void main() {
  Sub s = Sub(10);
  s.describe(); // Inherited from Super
  s.specific(); // Defined in Sub
  
  Super upcast = s; // Valid assignment
}

Constructors

Extension types allow both primary and named constructors.
  1. Primary Constructor: Declared immediately after the type name. It must accept exactly one positional parameter (the representation object).
  2. Named/Factory Constructors: Can be defined in the body to provide alternative initialization logic.
extension type Temperature(double degrees) {
  // Redirecting named constructor
  Temperature.freezing() : this(0.0);

  // Factory constructor
  factory Temperature.parse(String input) {
    return Temperature(double.parse(input));
  }
}

Member Resolution and Object Members

Extension types have specific rules regarding member resolution and the core Object members.

Shadowing Implemented Members

If an extension type uses implements to expose the representation type’s interface, it can still shadow specific members by redeclaring them in the extension type body. The extension type’s declaration takes precedence statically.
extension type Logic(bool value) implements bool {
  // Shadows the 'operator &' from bool
  bool operator &(bool other) {
    print("Custom logic invoked");
    return value && other;
  }
}

Restriction on Object Members

Extension types cannot declare members with the same names as the members of Object: toString, hashCode, runtimeType, and noSuchMethod. Because extension types are erased at runtime, calls to these methods always resolve to the underlying representation object’s implementation. This ensures that runtime behavior (like equality checks and string representation) remains consistent with the actual runtime type.
extension type Wrapper(int i) {
  // ERROR: Extension types cannot declare 'toString'.
  // String toString() => "Wrapper($i)"; 
}

void main() {
  var w = Wrapper(10);
  // Calls int.toString()
  print(w.toString()); // Output: 10
}
Master Dart with Deep Grasping Methodology!Learn More