Soundness in Dart
What is soundness?
Soundness ensures that the program never comes across any invalid state that may lead to abrupt termination of the program. As its name suggests it ensures perfect type matching that is the program can never get into the state where an expression evaluated to a value that doesn't match the static type of the expression.
For example if the static type of expression is String, we can only get a string value when we evaluate it at the runtime. Like in any other programming languages such as Java and C#, the Dart type system is also sound. It ensures the soundness using both static checking and runtime checking.
For example, if we asign 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.
Advantages of Soundness
There are various benefits of using sound type system:
- 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.
Let's look at these rules in more detail, with examples using the following types of positions :
Animal sequence where the largest species is Animal and the subspecies are Snake, Cat, and Monkey. The cat has less breeds of Lion and Tiger
Use sound return types when overriding methods
The return type of a method in the subclass should be the same type or sub-type return method in the superclass. Consider how to find animals in the animal category :
class Animal {
void chase( Animal a ) { . . . }
Animal get parent => . . .
}
The parent getter( ) method returns ‘ Animal ’. In the ‘ Monkey ’ subclass, you can change the Getter recovery type with Monkey ( or any other sub-type of Animal ), but unrelated type is not allowed.
class Monkey extends Animal {
@override
void chase( Animal a ) { . . . }
@override
Monkey get parent => . . .
}
class Monkey extends Animal {
@override
void chase( Animal a ) { . . . }
@override
Root get parent => . . .
}
Use sound parameter types when overriding methods
The output path parameter must have the same type or maximum corresponding parameter type in the superclass. " Do not override " the parameter type by substituting for the minimum version of the original parameter.
Note :
If you have good reason to use a subtype, you can use a covariant keyword.
Consider the ( Animal ) chase method of the Animal category:
class Animal {
void chase( Animal a ) { . . . }
Animal get parent => . . .
}
The chase( ) method takes an Animal. A ‘ Monkey ’ chases anything. It is okay to write down the chase( ) method of taking anything ( Object ).
class Monkey extends Animal {
@override
void chase( Object a ) { . . . }
@override
Animal get parent => . . .
}
The following code tightens the parameter on the chase( ) path from Animal to Mouse, a subclass ‘ Animal ’.
class Mouse extends Animal { . . . }
class Cat extends Animal {
@override
void chase( Mouse x ) { . . . }
}
This code is not a safe type because you will be able to identify the cat and send it after the Snake :
Animal a = Cat( ) ;
a.chase( Snake( ) ) ; // Not type safe or feline safe.
Do not use the dynamic list as a typed list.
A dynamic list is great if you want to have a list of different things in it. However, you cannot use dynamic lists as a typed list.
This rule also applies in cases of the general species.
The following code creates a dynamic list of Dogs, and gives it a list of Cat types, which creates 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
}
Runtime checks
Runtime checks in the Dart VM and dartdevc is dealing with security issues which the analyzer cannot handle.
For example, the following code does something different during operation because it is a mistake to publish a list of dogs in the cat list :
void main( ) {
List < Animals > animals = [ Dog ( ) ] ;
List < Cat > cats = animals as List < Cat > ;
}