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
aswirft 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