22

I was going through dart documentation and there I came across this code and this term covariant. I went through some documentation but I didn't get what is its function there. A detailed explained answer is always appreciated.

class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  @override
  void chase(covariant Mouse x) { ... }
}
Manishyadav
  • 1,361
  • 1
  • 8
  • 26

4 Answers4

41

In Dart, if you override a superclass method, the arguments of the override method must have the same type as the original.

Since Animal.chase in your example accepts an argument of Animal, you must do the same in your override:

class Cat extends Animal {
  @override
  void chase(Animal x) { ... }
}

Why? Imagine if there was no such restriction. Cat could define void chase(Mouse x) while Dog could define void chase(Cat x). Then imagine you have a List<Animal> animals and you call chase(cat) on one of them. If the animal is a dog, it'll work, but if the animal is cat, Cat is not a Mouse! The Cat class has no way to handle being asked to chase another Cat.

So you're forced to use void chase(Animal x). We can simulate a void chase(Mouse x) type signature by adding a runtime type check:

void chase(Animal x) {
  if (x is Mouse) {
    /* do chase */
  } else {
    /* throw error */
  }
}

It turns out this is a fairly common operation, and it would be nicer if it could be checked at compile time where possible. So Dart added a covariant operator. Changing the function signature to chase(covariant Mouse x) (where Mouse is a subclass of Animal) does three things:

  1. Allows you to omit the x is Mouse check, as it is done for you.
  2. Creates a compile time error if any Dart code calls Cat.chase(x) where x is not a Mouse or its subclass — if known at compile time.
  3. Creates a runtime error in other cases.

Another example is the operator ==(Object x) method on objects. Say you have a class Point:

You could implement operator== this way:

class Point {
  final int x, y;
  Point(this.x, this.y);

  bool operator==(Object other) {
    if (other is Point) {
      return x == other.x && y == other.y;
    } else {
      return false;
    }
  }
}

But this code compiles even if you compare Point(1,2) == "string" or a number or some other object. It makes no sense to compare a Point with things that aren't Points.

You can use covariant to tell Dart that other should be a Point, otherwise it's an error. This lets you drop the other is Point part, too:

bool operator==(covariant Point other) =>
  x == other.x && y == other.y;

Why is it called 'covariant'?

Covariant is a fancy type theory term, but it basically means 'this class or its subclasses'. Put another way, it means types that are equal or lower in the type hierarchy.

You are explicitly telling Dart to tighten the type checking of this argument to a subclass of the original. For the first example: tightening Animal to Mouse; for the second: tightening Object to Point.

Useful related terms are contravariant, which means types equal or higher in the type hierarchy, and invariant, which means exactly this type.

For more information, this Stack Overflow question is a good resource.

rjh
  • 49,276
  • 4
  • 56
  • 63
  • "Cat class has no way to handle being asked to chase another Cat" I see cats chasing cats all the time :)) – Alex Aug 22 '23 at 05:54
14

Just try to remove the key word covariant and it will become self explanatory.

You will receive a compiler error that you are overiding a method with mismatch parameter type Expected: Animal, Actual: Mouse

However, Mouse is a subtype of Animal, so if you want to allow this case without error, add the covariant keyword

Before enter image description here

After

enter image description here

Here you can see the Mouse is subtype of animal

lava
  • 6,020
  • 2
  • 31
  • 28
TSR
  • 17,242
  • 27
  • 93
  • 197
0

By using the covariant keyword, you disable the type-check and take responsibility for ensuring that you do not violate the contract in practice.

As you can see in the example, if you are overriding a method, its params should also be the same. But if you are using covariant, it will allow you to use Mouse instead of Animal.

Abhishek Doshi
  • 465
  • 2
  • 5
  • 2
    No, technically, you don't disable type checking, you are telling dart that this parameter can be Mouse, disabling type check means allowing any parameter like an Boolean or String or Num – TSR Feb 23 '22 at 13:24
  • The type checking is still done, too - at compile time if possible, otherwise at run time. – rjh Oct 17 '22 at 16:56
0

Covariant is a keyword that allows you to override a method with a new type of parameter that is a subclass of the original type. This is handy when you want more precise behavior for different sorts of arguments without breaking compatibility with the superclass function.

class A {}

class B extends A {
}

class C extends B {
}

class D extends A {}

These are some simple classes that I will use to demonstrate different types of parameters.

// Create a class with a method
class P {
  void method(B b) {}
}

// You can create another class and override the 
// method without changing the type of the parameter.
class Q extends P {
  @override
  void method(B b) {}
}

// Without any problem, you can override 
// the method to take a superclass as an argument.
class R extends P {
  @override
  void method(A a) {}
}

// But if you override to take a subclass
// it gives an error.
class S extends P {
  @override
  void method(C c) {}  // Error
}

// You can avoid this error by 
// adding the `covariant` keyword.
class T extends P {
  @override
  void method(covariant C c) {}
}

// You can even add `covariant` with
// a superclass of the original type.
class U extends P {
  @override
  void method(covariant A a) {}
}

// But you can't use this to override
// methods to take arguments with unrelated types. 
// (Not in the hierarchy of the original type)
class V extends P {
  @override
  void method(covariant D d) {}  // Error
}
Ramesh-X
  • 4,853
  • 6
  • 46
  • 67