Solides Typsystem

Darts solides Typsystem (Sound Type System)

Kerndefinition

Dart ist eine solide statisch typisierte Sprache, abgesichert durch eine doppelte Schutzschicht: statische Prüfung zur Kompilierzeit und Laufzeitvalidierung. Der Wert einer Variable zur Laufzeit stimmt immer mit ihrem statisch deklarierten Typ überein.

Die Typregeln werden streng durchgesetzt, aber Typannotationen sind optional – der Compiler leitet Typen automatisch ab.

Statische Analyse während der Kompilierung fängt die meisten Typfehler bereits im Vorfeld ab.

Was bedeutet Solidität (Soundness)

Der Code kann niemals einen ungültigen Typzustand annehmen. Hat ein Ausdruck den statischen Typ String, liefert er zur Laufzeit ausschließlich Zeichenketten, keine anderen Datentypen. Vergleichbare Sprachen: Java, C#.

  • Abfangen zur Kompilierzeit: int a = "abc"; erzeugt sofort einen Fehler;
  • Abfangen zur Laufzeit: Der Typumwandlungsoperator as wirft eine Ausnahme bei Typunverträglichkeit.

Vier wesentliche Vorteile

Typfehler tauchen schon beim Kompilieren auf, schwer reproduzierbare Abstürze im Produktivbetrieb durch Typinkonsistenzen werden vermieden;

Bessere Lesbarkeit des Codes: Typen täuschen Entwickler nicht über die Datenstruktur;

Einfachere Wartbarkeit: Eine Änderung an einer Codestelle markiert die Typüberprüfung alle betroffenen Codeteile;

Höhere Effizienz der AOT-Kompilierung: Typinformationen ermöglichen dem Compiler, optimierten Maschinencode zu erzeugen.

Beispiele

Statischer Fehler durch dynamische Collections

// Erwartet Parameter vom Typ List<int>
void printInts(List<int> a) => print(a);

void main() {
  // Kein Generisch-Typ angegeben, automatisch als List<dynamic> abgeleitet
  final list = [];
  list.add(1);
  list.add('2'); // Dynamische Liste erlaubt gemischte Typen
  printInts(list); 
  // Kompilierfehler: List<dynamic> kann nicht an List<int> zugewiesen werden
}Code-Sprache: Dart (dart)

Explizite Deklaration generischer Einschränkungen

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

void main() {
  final list = <int>[]; // Generischer Typ int explizit festlegen
  list.add(1);
  // list.add('2'); // Kompilierfehler, Zeichenkette kann nicht eingefügt werden
  list.add(2);
  printInts(list); // Besteht statische Prüfung ohne Probleme
}Code-Sprache: Dart (dart)

Drei zentrale Regeln

1 Beim Überschreiben von Methoden muss der Rückgabetyp dem Originaltyp oder einem seiner Untertypen entsprechen (Produzentenregel)

Rückgabewerte von Oberklassenmethoden gelten als Produzenten. Unterklassen können spezifischere Untertypen zurückgeben, aber keine unzusammenhängenden Typen.

Beispiel

Gültiges Beispiel

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

class HoneyBadger extends Animal {
  @override
  HoneyBadger get parent => HoneyBadger(); // Untertyp, zulässig
}Code-Sprache: Dart (dart)

Ungültiges Beispiel

class Root {}
class HoneyBadger extends Animal {
  @override
  Root get parent => Root(); // Unverwandter Typ, Kompilierfehler
}Code-Sprache: Dart (dart)

2 Beim Überschreiben von Methoden muss der Parametertyp dem Originaltyp oder einem seiner Obertypen entsprechen

Methodenparameter sind Konsumenten. Unterklassen dürfen nur den Parameterbereich erweitern (auf Obertyp), nicht auf einen Untertyp einschränken – eine Einschränkung zerstört die Typsicherheit.

Gültiges Beispiel (Parameter erweitert auf Object)

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

class HoneyBadger extends Animal {
  @override
  void chase(Object a) {} // Object ist Obertyp von Animal, zulässig
}Code-Sprache: Dart (dart)

Ungültiges Beispiel (Parameter eingeschränkt auf Unterklasse Mouse)

class Mouse extends Animal {}
class Cat extends Animal {
  @override
  void chase(Mouse a) {} // Einschränkung des Parametertyps → statischer Fehler
}

void main() {
  Animal cat = Cat();
  cat.chase(Alligator()); // Theoretisch zulässige Übergabe, bricht Typsicherheit
}Code-Sprache: Dart (dart)

Regel 3: Dynamische generische Collections können nicht direkt an fest typisierte generische Collections zugewiesen werden

List<dynamic> und List<Dog> lassen sich nicht direkt an List<Cat> zuweisen und erzeugen einen Kompilierfehler. Zwischen unterschiedlichen generischen Collections gibt es keine implizite Umwandlung.

class Cat {}
class Dog {}

void main() {
  List<Cat> foo = <dynamic>[Dog()]; // Statischer Kompilierfehler
  List<dynamic> bar = <dynamic>[Dog(), Cat()]; // Zulässig, dynamische Liste mit gemischten Typen
}Code-Sprache: Dart (dart)

Laufzeit-Typprüfung

Statische Prüfungen decken nicht alle Fälle ab. Zur Laufzeit laufen zusätzliche Typprüfungen, die bei Typunpassung eine Ausnahme werfen.

Laufzeitfehler bei generischer Typumwandlung

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

void main() {
  List<Animal> animals = <Dog>[Dog()];
  List<Cat> cats = animals as List<Cat>; 
  // Laufzeitausnahme: List<Dog> kann nicht nach List<Cat> umgewandelt werden
}Code-Sprache: Dart (dart)

Implizite Downcast von dynamic (geprüft zur Laufzeit)

Die Zuweisung einer dynamic-Variable an einen konkreten Typ löst eine implizite Umwandlung aus, die zur Laufzeit geprüft wird; bei Abweichung wird ein Fehler ausgelöst.

int assumeString(dynamic object) {
  String string = object; // Zur Laufzeit prüfen, ob object ein String ist
  return string.length;
}

void main() {
  assumeString("test"); // Läuft fehlerfrei
  assumeString(123);    // Laufzeitfehler: int kann nicht zu String umgewandelt werden
}Code-Sprache: Dart (dart)

Implizite Umwandlungen deaktivieren (strikter Typmodus)

Aktiviere strikte Umwandlungen in analysis_options.yaml, um implizite Downcasts von dynamic zu blockieren

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

Typinferenz (Type Inference)

Dart leitet automatisch Typen für Variablen, Generics und Methodenrückgabewerte ab. Fehlen ausreichende Typinformationen, wird standardmäßig dynamic abgeleitet.

Einfache Variableninferenz

// Automatisch abgeleitet als Map<String, Object>
var arguments = {'argA': 'hello', 'argB': 42};

var x = 3; // x wird als int abgeleitet
x = 4.0;   // Fehler, int kann keinen double speichern

num y = 3; // Explizit deklariertes num, nimmt int und double auf
y = 4.0;   // ZulässigCode-Sprache: Dart (dart)

Inferenz generischer Konstruktoren / Collections

List<int> listOfInt = []; // Automatisch abgeleitet als <int>[]
var listOfDouble = [3.0]; // Abgeleitet als List<double>

var ints = listOfDouble.map((x) => x.toInt()); 
// x als double abgeleitet, Callback gibt int zurück → ints ist Iterable<int>Code-Sprache: Dart (dart)

Solides Typsystem

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert