What is soundness?

Domains: Dart

Soundness is about ensuring your program can’t get into certain invalid states. A sound type system means you can never get into a state where an expression evaluates to a value that doesn’t match the expression’s static type. For example, if an expression’s static type is String, at runtime you are guaranteed to only get a string when you evaluate it.

Dart’s type system, like the type systems in Java and C#, is sound. It enforces that soundness using a combination of static checking (compile-time errors) and runtime checks. For example, assigning a String to int is a compile-time error. Casting an Object to a string using as String fails with a runtime error if the object isn’t a string.

The benefits of soundness

A sound type system has several benefits:

  • Revealing type-related bugs at compile time. A sound type system forces code to be unambiguous about its types, so type-related bugs that might be tricky to find at runtime are revealed at compile time.
  • More readable code. Code is easier to read because you can rely on a value actually having the specified type. In sound Dart, types can’t lie.
  • More maintainable code. With a sound type system, when you change one piece of code, the type system can warn you about the other pieces of code that just broke.
  • Better ahead of time (AOT) compilation. While AOT compilation is possible without types, the generated code is much less efficient.

Tips for passing static analysis

Most of the rules for static types are easy to understand. Here are some of the less obvious rules:

  1. Use sound return types when overriding methods.
  2. Use sound parameter types when overriding methods.
  3. Don’t use a dynamic list as a typed list.

Let’s see these rules in detail, with examples that use the following type hierarchy:

https://dart.dev/guides/language/images/type-hierarchy.png" class="siteImg" style="float:left" alt="" title="">

Use sound return types when overriding methods

The return type of a method in a subclass must be the same type or a subtype of the return type of the method in the superclass. Consider the getter method in the Animal class:

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

The parent getter method returns an Animal. In the HoneyBadger subclass, you can replace the getter’s return type with HoneyBadger (or any other subtype of Animal), but an unrelated type is not allowed.

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

Use sound parameter types when overriding methods

The parameter of an overridden method must have either the same type or a supertype of the corresponding parameter in the superclass. Don’t “tighten” the parameter type by replacing the type with a subtype of the original parameter.

Note: If you have a valid reason to use a subtype, you can use the covariant keyword.

Consider the chase(Animal) method for the Animal class:

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

The chase() method takes an Animal. A HoneyBadger chases anything. It’s OK to override the chase() method to take anything (Object).

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

The following code tightens the parameter on the chase() method from Animal to Mouse, a subclass of Animal.

class Mouse extends Animal {...} 

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

This code is not type safe because it would then be possible to define a cat and send it after an alligator:

Animal a = Cat(); 
a.chase(Alligator()); // Not type safe or feline safe

Don’t use a dynamic list as a typed list

A dynamic list is good when you want to have a list with different kinds of things in it. However, you can’t use a dynamic list as a typed list.

This rule also applies to instances of generic types.

The following code creates a dynamic list of Dog, and assigns it to a list of type Cat, which generates an error during static analysis.

class Cat extends Animal { ... } 

class Dog extends Animal { ... } 

void main() { 
   List<Cat> foo = <dynamic>[Dog()]; // Error 
   List<dynamic> bar = <dynamic>[Dog(), Cat()]; // OK 
}

Similar pages

Page structure
Terms

Methods

Types

String

List

Getters and setters

Dart

int

Generics