Sistema de tipos sólido de Dart (Sound Type System)
Definición principal
Dart es un lenguaje de tipado estático sólido protegido por un doble mecanismo: comprobaciones estáticas en tiempo de compilación y validaciones en tiempo de ejecución. En ejecución, el valor almacenado en una variable siempre coincide con su tipo declarado de forma estática.
Las reglas de tipos se aplican de forma estricta, pero las anotaciones de tipo son opcionales; el compilador deduce los tipos automáticamente.
El análisis estático durante la compilación detecta la mayoría de los errores de tipo de antemano.
¿Qué es la solidez (Soundness)?
El código nunca puede entrar en un estado de tipo inválido. Si una expresión tiene el tipo estático String, solo devolverá cadenas de texto en ejecución, sin permitir otros tipos. Lenguajes equivalentes: Java, C#.
- Bloqueo en compilación:
int a = "abc";genera error inmediato; - Bloqueo en ejecución: El casting con
aslanza una excepción si los tipos no coinciden.
Cuatro ventajas clave
Los errores de tipo salen a la luz en compilación, evitando fallos en producción difíciles de reproducir causados por desajustes de tipos;
Mayor legibilidad del código, los tipos nunca engañan al desarrollador sobre la estructura de los datos;
Código más mantenible: al modificar un fragmento, las comprobaciones de tipo marcan todas las secciones afectadas;
Mejor rendimiento de compilación AOT, la información de tipos ayuda al compilador a generar código máquina optimizado.
Ejemplos
Error estático generado por colecciones dinámicas
// Espera recibir List<int>
void printInts(List<int> a) => print(a);
void main() {
// Sin declaración genérica, se deduce List<dynamic>
final list = [];
list.add(1);
list.add('2'); // Las listas dinámicas permiten mezclar tipos
printInts(list);
// Error de compilación: List<dynamic> no se puede asignar a List<int>
}Lenguaje del código: Dart (dart)
Declaración explícita de restricciones genéricas
void printInts(List<int> a) => print(a);
void main() {
final list = <int>[]; // Especificar genérico int explícitamente
list.add(1);
// list.add('2'); // Error compilación, no se puede insertar cadena
list.add(2);
printInts(list); // Pasa la comprobación estática correctamente
}Lenguaje del código: Dart (dart)
Tres reglas fundamentales
1 Al sobrescribir métodos, el tipo de retorno debe ser el original o uno de sus subtipos (regla del productor)
Los valores devueltos por métodos de la clase padre son productores. Las subclases pueden devolver subtipos más concretos, pero no tipos sin relación.
Ejemplo
Caso válido
class Animal {
Animal get parent => Animal();
}
class HoneyBadger extends Animal {
@override
HoneyBadger get parent => HoneyBadger(); // Subtipo, permitido
}Lenguaje del código: Dart (dart)
Caso inválido
class Root {}
class HoneyBadger extends Animal {
@override
Root get parent => Root(); // Tipo sin relación, error compilación
}Lenguaje del código: Dart (dart)
2 Al sobrescribir métodos, el tipo de parámetros debe ser el original o uno de sus supertipos
Los parámetros de método son consumidores. Las subclases solo pueden ampliar el rango de parámetros (usar supertipo); reducirlo a un subtipo rompe la seguridad de tipos.
Ejemplo válido (parámetro ampliado a Object)
class Animal {
void chase(Animal a) {}
}
class HoneyBadger extends Animal {
@override
void chase(Object a) {} // Object es supertipo de Animal, permitido
}Lenguaje del código: Dart (dart)
Ejemplo inválido (parámetro reducido a la subclase Mouse)
class Mouse extends Animal {}
class Cat extends Animal {
@override
void chase(Mouse a) {} // Reducir tipo de parámetro, error estático
}
void main() {
Animal cat = Cat();
cat.chase(Alligator()); // Teóricamente admite Alligator, genera inseguridad de tipos
}Lenguaje del código: Dart (dart)
Regla 3: No se puede asignar directamente una colección genérica dinámica a una colección genérica con tipo definido
List<dynamic> y List<Dog> no se pueden asignar directamente a List<Cat> y causan error de compilación. No existe conversión implícita entre colecciones genéricas distintas.
class Cat {}
class Dog {}
void main() {
List<Cat> foo = <dynamic>[Dog()]; // Error estático de compilación
List<dynamic> bar = <dynamic>[Dog(), Cat()]; // Válido, listas dinámicas permiten mezclar tipos
}Lenguaje del código: Dart (dart)
Validación de tipos en tiempo de ejecución
Las comprobaciones estáticas no cubren todos los escenarios; se ejecutan comprobaciones adicionales en ejecución que lanzan excepciones ante desajustes de tipos.
Error en ejecución por casting genérico
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
void main() {
List<Animal> animals = <Dog>[Dog()];
List<Cat> cats = animals as List<Cat>;
// Excepción en ejecución: No se puede convertir List<Dog> a List<Cat>
}Lenguaje del código: Dart (dart)
Downcast implícito desde dynamic (validado en ejecución)
Asignar una variable dynamic a un tipo concreto activa una conversión implícita comprobada en ejecución; si los tipos no coinciden, salta un error.
int assumeString(dynamic object) {
String string = object; // Comprobación en ejecución si object es String
return string.length;
}
void main() {
assumeString("test"); // Se ejecuta sin problemas
assumeString(123); // Error ejecución: int no se puede convertir a String
}Lenguaje del código: Dart (dart)
Deshabilitar conversiones implícitas (modo de tipos estricto)
Habilita las conversiones estrictas en analysis_options.yaml para bloquear downcasts implícitos desde dynamic
analyzer:
language:
strict-casts: trueLenguaje del código: JavaScript (javascript)
Inferencia de tipos (Type Inference)
Dart deduce automáticamente los tipos de variables, genéricos y valores de retorno de métodos. Si no hay información suficiente, el tipo por defecto es dynamic.
Inferencia de variables simples
// Deducido automáticamente como Map<String, Object>
var arguments = {'argA': 'hello', 'argB': 42};
var x = 3; // x se deduce como int
x = 4.0; // Error, int no puede almacenar double
num y = 3; // Declaración explícita num, admite int y double
y = 4.0; // VálidoLenguaje del código: Dart (dart)
Inferencia de constructores genéricos / colecciones
List<int> listOfInt = []; // Se deduce automáticamente <int>[]
var listOfDouble = [3.0]; // Deducido List<double>
var ints = listOfDouble.map((x) => x.toInt());
// x deducido double, callback devuelve int → ints es Iterable<int>Lenguaje del código: Dart (dart)
Sistema de tipos sólido