Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

An extension type is a compile-time, zero-cost abstraction that wraps an existing underlying type with a new, distinct static type interface. At runtime, the extension type is completely erased, and the underlying representation is used directly, incurring no object allocation or indirection overhead.

Syntax

An extension type declaration requires a name and exactly one underlying representation variable, which acts as the primary constructor.
extension type ExtensionTypeName(UnderlyingType representationName) {
  // Optional: methods, getters, setters, and additional constructors
}
Example:
extension type IdNumber(int value) {
  bool get isValid => value > 0;
  void printId() => print('ID: $value');
}

Technical Mechanics

1. Interface Encapsulation

By default, an extension type completely hides the interface of its underlying type. If you call a method on the extension type that exists on the underlying type but is not explicitly declared in the extension type, the compiler will throw an error.
extension type IdNumber(int value) {}

void main() {
  IdNumber id = IdNumber(42);
  // id.isEven; // Compile-time error: 'isEven' is not defined for 'IdNumber'
}

2. The implements Clause

To expose the underlying type’s interface, an extension type can implement its representation type. This establishes a subtyping relationship where the extension type becomes a static subtype of the underlying type.
extension type IdNumber(int value) implements int {
  // Inherits all members of 'int' (e.g., isEven, +, -, etc.)
}
An extension type can also implement other extension types, provided they share the same underlying representation type or a supertype of it.

3. State Restrictions

Extension types are strictly limited to their single representation variable. They cannot declare additional instance variables. They can, however, declare static variables.
extension type UserToken(String token) {
  // String _cache; // Compile-time error: Extension types can't declare instance fields.
  static const int maxTokenLength = 256; // Allowed
}

4. Constructors

The representation variable automatically generates a primary constructor. You can define additional named or factory constructors, but they must ultimately initialize the single representation variable.
extension type Point(List<int> coordinates) {
  // Named constructor delegating to the primary constructor
  Point.origin() : this([0, 0]); 
  
  // Factory constructor
  factory Point.fromString(String x, String y) {
    return Point([int.parse(x), int.parse(y)]);
  }
}

Type Assignability and Casting

Because extension types are static constructs, the Dart analyzer enforces strict rules regarding assignability:
  • Underlying to Extension: You cannot implicitly assign the underlying type to the extension type. You must use the constructor or an explicit cast.
  • Extension to Underlying: You can implicitly assign an extension type to its underlying type only if the extension type implements the underlying type. Otherwise, it requires an explicit cast or accessing the representation variable.
When using the as operator, Dart performs a runtime cast. Because the extension type is erased at runtime, casting to an extension type is evaluated exactly as casting to its underlying representation type.
extension type WrappedInt(int value) {}

void main() {
  int a = 10;
  
  // WrappedInt w1 = a; // Compile-time error
  WrappedInt w2 = WrappedInt(a); // OK
  
  // OK: This is a runtime cast. Due to type erasure, 
  // it is evaluated at runtime exactly as `a as int`.
  WrappedInt w3 = a as WrappedInt; 

  // int b = w2; // Compile-time error (unless WrappedInt implements int)
  int c = w2.value; // OK (Accessing representation)
  int d = w2 as int; // OK
}

Runtime Erasure and Type Checking

Because extension types are erased during compilation, runtime type checks (is) and runtime type queries (.runtimeType) evaluate against the underlying representation type, not the extension type. The is operator is always a runtime type test. When you check if an object is an extension type, the compiler erases the extension type and performs the runtime check against the underlying type.
extension type SecretString(String text) {}

void main() {
  var secret = SecretString('hidden');
  
  // true: Evaluated at runtime exactly as `secret is String` due to type erasure
  print(secret is SecretString); 
  
  // true: The underlying representation is a String
  print(secret is String);       
  
  // String: The runtime type is the underlying type
  print(secret.runtimeType);     
}
Master Dart with Deep Grasping Methodology!Learn More