La desestructuración (o destructuring) es la capacidad de extraer valores de estructuras de datos complejas —como records, lists, maps o incluso objetos personalizados— y asignarlos a variables individuales en una sola operación. En lugar de acceder a cada propiedad mediante índices o llaves de forma repetitiva, utilizas un pattern (patrón) que describe la forma de los datos.
Esta funcionalidad, introducida en [disponible desde Dart 3.0], funciona mediante el pattern matching (coincidencia de patrones). El motor de Dart analiza la estructura del valor en tiempo de ejecución y, si el patrón coincide, “descompone” el objeto para extraer sus componentes. Esto no es solo una mejora sintáctica para evitar el boilerplate de código; es una herramienta que permite que el tipado estructural sea parte de la lógica de tu programa.
Cuando usas la desestructuración en una declaración de variable (por ejemplo, con final o var), el patrón debe ser irrefutable. Esto significa que el patrón debe garantizar que la coincidencia siempre ocurrirá. Si el patrón es refutable (es decir, que podría no coincidir si la estructura es distinta), Dart te obligará a usarlo en un contexto de control de flujo (como un if-case o un switch) para manejar el caso de que la coincidencia falle. De lo contrario, corres el riesgo de obtener errores en tiempo de ejecución al intentar desestructurar algo que no tiene la forma esperada.
class Usuario {
final String nombre;
final int edad;
final String correo;
Usuario(this.nombre, this.edad, this.correo);
}
void main() {
// 1. Records: Posicionales y Nombrados
final persona = (nombre: 'Elena', edad: 28);
final (:nombre, :edad) = persona; // Destructuring de campos nombrados
final (ciudad, pais) = ('Madrid', 'España'); // Destructuring posicional
// 2. Lists: Posición y el operador spread (...)
final notas = [9, 8, 7, 10, 5];
final [primera, segunda, ...resto] = notas;
// 3. Maps: Extracción por llave
final config = {'tema': 'oscuro', 'fuente': 'JetBrains Mono'};
final {'tema': temaAplicado, 'fuente': fuente} = config;
// 4. Object patterns y anidamiento complejo
final admin = Usuario('Marcos', 35, 'marcos@dev.com');
final Usuario(:nombre: nombreAdmin, :email) = admin; // Error intencionado para la sección de errores
// Ejemplo de anidamiento real: Un mapa que contiene una lista de objetos
final baseDeDatos = {
'staff': [
Usuario('Ana', 30, 'ana@corp.com'),
Usuario('Luis', 45, 'luis@corp.com'),
]
};
// Destructuring anidado en un loop
for (final {'staff': [Usuario(:name: primerNombre, ...otros), ...]} in [baseDeDatos]) {
print('El primer empleado es $primerNombre');
print('Los demás empleados están en: $otros');
}
}
Análisis del código
En el ejemplo anterior, observa cómo final (:nombre, :edad) = persona; utiliza una sintaxis abreviada. Cuando el nombre de la variable es igual al nombre del campo en el record, puedes usar la sintaxis :nombre para decirle a Dart: “busca el campo nombre y asígnalo a una nueva variable llamada nombre“.
En la desestructuración de lists, el uso de ...resto emplea el patrón de propagación. Esto es crucial cuando no necesitas todos los elementos, pero quieres capturar el excedente en una lista adicional, evitando tener que calcular manualmente el tamaño de la lista original.
Para los maps, la desestructuración busca las llaves especificadas ('tema' y 'fuente'). Si la llave no existiera en el mapa, el programa fallaría si estamos en una declaración de variable, ya que el patrón no sería irrefutable.
El caso más potente es el anidamiento en el for loop. Aquí, estamos recorriendo una lista de mapas ([baseDeDatos]). El patrón {'staff': [Usuario(:name: primerNombre, ...otros), ...]} hace varias cosas a la vez:
1. Accede a la llave 'staff' del mapa.
2. Dentro de esa lista, busca el primer elemento y comprueba que sea un objeto de tipo Usuario.
3. Extrae el campo nombre de ese objeto y lo asigna a primerNombre.
4. Captura todos los demás elementos de la lista de usuarios en la variable otros.
Este nivel de destructuring permite navegar por estructuras JSON o modelos de datos complejos sin una sola línea de código de acceso manual.
El error frecuente
Un error común ocurre al intentar desestructurar una colección con un patrón que asume una estructura fija, pero se usa en una declaración de variable irrefutable.
void errorDeEstructura() {
final lista = [1, 2];
// ERROR: Intentar desestructurar 3 elementos de una lista de 2.
// El patrón [a, b, c] es refutable porque la lista podría no tener 3 elementos.
final [a, b, c] = lista;
}
En este caso, el compilador de Dart lanzará un error de tipo o una excepción en tiempo de ejecución porque el patrón [a, b, c] es refutable (la coincidencia no está garantizada). Para usar este patrón de forma segura, deberías usar un if-case:
if (lista case [a, b, c]) {
print('Tiene tres elementos: $a, $b, $c');
} else {
print('La lista no tiene exactamente tres elementos');
}
N° 86