functioning of covariant in flutter

Viewed 1365

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) { ... }
}
3 Answers

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

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.

In Dart, if you override a superclass method, you must keep the type the same. 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? Because changing the type would make the subclass incompatible with the superclass. For example say you made a list containing both Animals and Cats (type List<Animal>. Calling list[i].chase(cat) would be valid for Animals but invalid for Cats!

If you want the Cat to only chase animals to type Mouse, you would then add 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. 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 skip 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 x 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'. You are explicitly telling Dart to tighten the type checking of this argument to a subclass of the original. Ffor the first example: tightening Animal to Mouse; for the second: tightening Object to Point.

Related