Soundness & Subtyping in Dart Generics— Part II

Tarun Chawla
4 min readMay 7, 2022

What is Soundness in Dart?

In languages like Dart, C++, Kotlin, Swift etc, the type of expression is known at compile-time and we are calling it a static type. These languages are also called statically typed languages.

Soundness means ensuring your program does not get into certain invalid states. A sound system makes sure that the expression evaluates to a value and that its type is the same as the expression's static type.

Benefits of Soundness:

  1. Catching type errors even before compiling it with the aid of IDEs like Intellij, Visual Studio code etc.
  2. Readable code.
  3. Maintainable code: If you are creating type related issues somewhere else, IDE will catch it for you.
  4. Better ahead of time compilation(AOT) as it leads to efficient generation of low-level code before your code is executed.

How does overriding work in Dart while returning a type in a function?

Please bear with me, I promise this will be critical for your generics understanding.

Here is a class:

class Animal {
Animal get parent => ...
void chase(Animal a){ ... }
}

Now let's say you want to extend this you created below class:

class Cat extends Animal {
@override
Animal get parent => ...
}

You can see that you are trying to override the parent getter. Dart allows any subtype of Animal (base class getter return type) in the overridden method. In this case, you could have returned Cat, Lion etc.

class Cat extends Animal {
@override
Cat get parent => ...
}

Dart is okay with any subtype as it does not bother as long as it has the essence of an Animal. If you try to return Object, it will complain as Dart wants to return Animal in some form otherwise it is not an override.

Tip: Always return the same type or subtype of the return type while overriding.

How does overriding work in Dart in parameter type in a function?

Let's look at the chase function now. It says I accept an Animal to chase it and hunt it down.

class Animal {
Animal get parent => ...
void chase(Animal a){ ... }
}

If you try to override with any subtype then basically you are tightening the scope further by saying now I won’t accept anything other than the new subtype.

class Cat {
Cat get parent => ...
@override
void chase(Cat a) { ... } //Here dart will complain as it is not a valid override.
}

Here, Dart is saying bro/sis I don’t want you to tighten this as it defeats the purpose of overriding, I want Animal to be allowed in the chase function, not its subtype. Now you will be wondering why it allows supertype, it will get more clear when you understand the concept of consumer and producer further in the article.

Tip: Always use the same type or its supertype in the parameter of the function you are overriding.

Substituting types

Ok for overriding we understand now what type we can choose. But what about assignments and passing arguments to functions. Dart allows certain sub and supertypes. It's easier to think in terms of producers and consumers. A consumer absorbs a type and a producer generates a type.

Animal animal = Cat();

Here left-hand side(LHS) is the consumer and the right-hand side(RHS) is the producer. Logically any supertype as a consumer will make sense as the producer will always have supertype behaviour (even if it's overridden). In programming terms, you can call all Animal methods and properties in the above example without any runtime or compile-time issues. Dart says consumers can have the same type as producers or any supertype will work.

Tip: Consumers can have the same or super type of producer.

For producers, it is the reverse. Any subtype can be used for producers.

Animal animal = Cat()
Animal animal = Lion()

See, Cat and Lion both fit as a producer as they both are supertypes and the same logic which I shared above explains why this is allowed.

Now the question is, are the rules we discussed above fit for generic type?

And the answer is yes.

In Dart types are not erased for Generic types, it is much easier to work in Dart with generics. What this means is that, if you have a list of integers in Java and Dart then in Dart you can check if the type is List<int> but in Java type gets erased at run time and therefore you can’t tell what is the underlying type of the list. In Java, you can only tell if this is a list or not.

In Dart, this is absolutely fine.

List<Animal> animalList = <Cat>[];

I think with this article you will build a strong foundation of Generics in Dart.

References:

  1. Part-I : https://tarunengineer.medium.com/generics-in-dart-part-i-bd2ca66e0e57
  2. https://dart.dev/guides/language/type-system#generic-type-assignment

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response