Sistema de tipos sólido

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 as lanza 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

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *