Sound Type System

Dart Sound Type System

Core Definition

Dart is a sound statically typed language secured by dual safeguards: static compile-time checks plus runtime validation. At runtime, the value stored in any variable will always match its statically declared type.

Type rules are enforced rigorously, yet type annotations are optional — types are automatically inferred by the compiler.

Static analysis during compilation catches most type errors in advance.

What Is Soundness

Your code can never enter an invalid type state. If an expression has a static type of String, it will only yield string values at runtime with no other types allowed. Comparable languages: Java, C#.

  • Compile-time interception: int a = "abc"; throws an immediate error.
  • Runtime interception: Casting via as throws an exception if type matching fails.

Four Key Benefits

Type bugs surface at compile time, avoiding hard-to-reproduce production crashes caused by type mismatches.

Improved code readability — types never mislead developers about data shapes.

Easier maintainability: Editing one piece of code triggers type checks that flag every dependent section affected by the change.

Higher AOT compilation performance: Type metadata helps the compiler generate more optimized machine code.

Examples

Static error triggered by dynamic collections

// Expects List<int>
void printInts(List<int> a) => print(a);

void main() {
  // No generic declaration, inferred as List<dynamic>
  final list = [];
  list.add(1);
  list.add('2'); // Dynamic list permits mixed types
  printInts(list); 
  // Compile error: List<dynamic> cannot be assigned to List<int>
}Code language: Dart (dart)

Explicit generic constraints declaration

void printInts(List<int> a) => print(a);

void main() {
  final list = <int>[]; // Explicitly specify generic int
  list.add(1);
  // list.add('2'); // Compile error, cannot insert string
  list.add(2);
  printInts(list); // Passes static validation normally
}Code language: Dart (dart)

Three Core Rules

1. When overriding methods, the return type must be the original type or its subtype (Producer Rule)

Return values of superclass methods act as producers. Subclasses may return more specific subtypes but cannot return unrelated types.

Example

Valid Case

class Animal {
  Animal get parent => Animal();
}

class HoneyBadger extends Animal {
  @override
  HoneyBadger get parent => HoneyBadger(); // Valid subtype
}Code language: Dart (dart)

Invalid Case

class Root {}
class HoneyBadger extends Animal {
  @override
  Root get parent => Root(); // Unrelated type, compile error
}Code language: Dart (dart)

2. When overriding methods, parameter types must match the original type or its supertype

Method parameters are consumers. Subclasses may only widen parameter types to supertypes; narrowing to subtypes breaks type safety.

Valid example (parameter widened to Object)

class Animal {
  void chase(Animal a) {}
}

class HoneyBadger extends Animal {
  @override
  void chase(Object a) {} // Object is supertype of Animal, valid
}Code language: Dart (dart)

Invalid example (parameter narrowed to subclass Mouse)

class Mouse extends Animal {}
class Cat extends Animal {
  @override
  void chase(Mouse a) {} // Narrowed parameter type, static error
}

void main() {
  Animal cat = Cat();
  cat.chase(Alligator()); // Hypothetically allows Alligator input, causing unsafe typing
}Code language: Dart (dart)

Rule 3: Dynamic generic collections cannot be directly assigned to concrete generic collections

List<dynamic> and List<Dog> cannot be assigned directly to List<Cat> and will throw compile errors. Implicit conversion is unsupported between different generic collection types.

class Cat {}
class Dog {}

void main() {
  List<Cat> foo = <dynamic>[Dog()]; // Static compile error
  List<dynamic> bar = <dynamic>[Dog(), Cat()]; // Valid, dynamic list allows mixed types
}Code language: Dart (dart)

Runtime Type Validation

Static checks do not cover all scenarios. Additional runtime type validation runs and throws exceptions upon mismatches.

Runtime error from generic casting

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

void main() {
  List<Animal> animals = <Dog>[Dog()];
  List<Cat> cats = animals as List<Cat>; 
  // Runtime exception: Cannot cast List<Dog> to List<Cat>
}Code language: Dart (dart)

Implicit downcast from dynamic (runtime checked)

Assigning a dynamic variable to a concrete type triggers implicit conversion validated at runtime, with exceptions thrown for mismatched types.

int assumeString(dynamic object) {
  String string = object; // Runtime check verifying object is String
  return string.length;
}

void main() {
  assumeString("test"); // Executes normally
  assumeString(123);    // Runtime error: int cannot be cast to String
}Code language: Dart (dart)

Disable Implicit Casts (Strict Type Mode)

Enable strict casting inside analysis_options.yaml to block implicit downcasts from dynamic

analyzer:
  language:
    strict-casts: trueCode language: JavaScript (javascript)

Type Inference

Dart automatically infers types for variables, generics, and method return values. If insufficient type information exists, the type defaults to dynamic.

Basic variable inference

// Automatically inferred as Map<String, Object>
var arguments = {'argA': 'hello', 'argB': 42};

var x = 3; // x inferred as int
x = 4.0;   // Error, int cannot store double

num y = 3; // Explicitly declared num, accepts int/double
y = 4.0;   // ValidCode language: Dart (dart)

Generic constructor / collection inference

List<int> listOfInt = []; // Inferred as <int>[]
var listOfDouble = [3.0]; // Inferred List<double>

var ints = listOfDouble.map((x) => x.toInt()); 
// x inferred double, callback returns int → ints inferred Iterable<int>Code language: Dart (dart)

Sound Type System

Leave a Reply

Your email address will not be published. Required fields are marked *