Dominando variables: var, final, const y late en Dart

En Dart, declarar una variable es, en esencia, establecer un contrato con el compilador sobre cómo se tratará un dato en memoria. Si usas tipos explícitos como String o int, estás siendo taxativo: le dices exactamente qué esperar. Si prefieres la brevedad, usas var para que el compilador deduzca el tipo mediante la inferencia de tipos; una vez que var x = 10; ha sido procesado, x es un int y no puedes asignarle un String sin que el compilador te detenga.

La verdadera diferencia en la arquitectura de tu código reside en la inmutabilidad. Usamos final para valores que, una vez asignados en tiempo de ejecución, no cambiarán; es ideal para datos que vienen de una API o de una función. Por el contrario, const es para valores que deben ser conocidos desde el momento en que compilas el programa (como un número de versión o un valor matemático). La distinción es crítica: si intentas usar const con algo que solo se conoce al ejecutar, como DateTime.now(), el compilador fallará porque const requiere una evaluación constante en tiempo de compilación.

Si trabajas con clases, verás que a veces necesitas declarar una variable pero no puedes darle un valor en el mismo instante de la creación del objeto; ahí es donde entra late, una promesa al compilador de que la variable se inicializará antes de usarse. Si decides usar dynamic, estás renunciando a las garantías del sistema de tipos: el compilador dejará de verificar si lo que intentas hacer con esa variable es válido, delegando ese error al momento de la ejecución. Usar dynamic en exceso es una invitación a que tu código sea frágil y difícil de mantener.

// Una constante de nivel superior (top-level) conocida en compilación
const String nombreApp = 'Gestor de Inventario';

class Producto {
  final int id; // Inmutable: no puede cambiar tras el constructor
  late String nombre; // Se inicializará después de crear el objeto
  String? categoria; // Nullable: puede ser nulo explícitamente

  Producto(this.id);
}

void main() {
  // Tipos explícitos para mayor claridad en lógica de negocio
  int stockActual = 150;

  // Inferencia de tipo: el compilador sabe que es un String
  var estado = 'Activo';

  // Final: se asigna en tiempo de ejecución y no puede reasignarse
  final DateTime fechaRegistro = DateTime.now();

  // Const: valor inmutable y conocido desde la compilación
  const double impuesto = 0.16;

  // Dynamic: permite cambiar el tipo, pero rompe la seguridad de tipos
  dynamic etiquetaDinamica = 'Lote A';
  etiquetaDinamica = 500; // No hay error de compilación, pero es peligroso

  final producto = Producto(1024);
  producto.nombre = 'Laptop Pro'; // Funciona gracias a 'late'
  producto.categoria = 'Electrónica';

  print('--- $nombreApp ---');
  print('Producto: ${producto.nombre} (ID: ${producto.id})');
  print('Stock: $stockActual | Impuesto: ${impuesto * 100}%');
  print('Fecha: $fechaRegistro | Etiqueta: $etiquetaDinamica');
}

En el ejemplo anterior, nombreApp es una constante global que vive en el binario. Dentro de main, hemos definido stockActual con su tipo explícito para evitar ambigüedades, mientras que estado usa var, confiando en que el compilador hará el trabajo pesado. La variable fechaRegistro es final porque DateTime.now() es una operación de tiempo de ejecución; no podemos saber qué hora será hasta que el programa corra. Sin embargo, impuesto es const, lo que permite al compilador optimizar su uso.

En la clase Producto, id es final, lo que garantiza que la identidad de un objeto no cambie nunca, una práctica vital para la integridad de datos. El campo nombre está marcado como late; esto permite que el objeto se instancie sin ese valor, pero nos obliga a asignarlo antes de cualquier operación de lectura. Si intentas acceder a categoria sin haberle asignado algo, no habrá error porque es nullable (String?), pero si intentas usar nombre antes de asignarle el valor 'Laptop Pro', el programa lanzará una excepción en tiempo de ejecución. Por último, observa cómo etiquetaDinamica cambia de String a int sin quejarse, lo que demuestra por qué dynamic es una zona de peligro: si intentaras llamar a .toUpperCase() después de asignarle el 500, el programa fallaría.

El error frecuente

Un error clásico al trabajar con late es olvidar que, aunque le prometes al compilador que inicializarás la variable, el sistema no puede verificarlo.

late String descripcion;

void main() {
  // Error: LateInitializationError en tiempo de ejecución
  // Intentamos leer la variable antes de haberle asignado un valor.
  print(descripcion); 
}

Elegir la mutabilidad correcta es tu primera línea de defensa contra efectos secundarios inesperados.

11

Dejar un comentario

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

Scroll al inicio