Covariant and Contravariant Types

Covariant and Contravariant Types

About

  • Covariance: Preserves inheritance (return types)
  • Contravariance: Reverses inheritance (parameters)
  • Dart 2.x: Sound type system
  • Use Cases: Flexible yet type-safe APIs

Main Topics

  1. Return Type Covariance

    • Definition: Subclass narrows return type

    • Example:

      abstract class Animal {
        Animal clone();
      }
      
      class Cat extends Animal {
        @override
        Cat clone() => Cat(); // More specific
      }
  2. Parameter Types

    • Definition: Normally invariant

    • Example:

      class Printer {
        void print(Animal a) {...}
      }
      
      class CatPrinter extends Printer {
        // Can't override with void print(Cat c)
      }
  3. covariant Keyword

    • Definition: Explicit override

    • Example:

      class AnimalShelter {
        void adopt(covariant Animal a) {}
      }
      
      class CatShelter extends AnimalShelter {
        @override
        void adopt(Cat c) {} // Allowed
      }
  4. Generic Variance

    • Definition: Collection types
    • Example:
      List<Cat> cats = <Cat>[];
      List<Animal> animals = cats; // Error in Dart 2
  5. Type Safety

    • Definition: Runtime checks
    • Example:
      void execute(void Function(Animal) op) {
        op(Cat()); // Safe
      }

How to Use

  • Return Types: Narrow when possible
  • Parameters: Use covariant carefully
  • Generics: Understand invariance
  • Safety: Prefer sound code

How It Works

  1. Compilation: Static checks
  2. Runtime: Additional checks
  3. Soundness: Dart 2 guarantee
  4. Performance: Minimal overhead

Example:

abstract class AnimalHandler {
  void handle(covariant Animal a);
}

class CatHandler extends AnimalHandler {
  @override
  void handle(Cat c) { // Allowed due to covariant
    print('Handling cat');
  }
}

void main() {
  AnimalHandler handler = CatHandler();
  handler.handle(Cat()); // Valid
  // handler.handle(Dog()); // Runtime error
}

Conclusion

Dart’s type system carefully balances flexibility and safety through covariance in return types and explicit covariant parameter overrides. Understanding these variance concepts helps design APIs that are both type-safe and convenient to use while leveraging polymorphism.