29/05/2023
En el mundo del desarrollo de smart contracts con Solidity, la organización y la claridad del código no son solo buenas prácticas, son fundamentales para la seguridad y la eficiencia. Dos de las herramientas más poderosas que nos ofrece Solidity para lograr esto son los structs (estructuras) y los enums (enumeraciones). Estos nos permiten crear nuestros propios tipos de datos complejos, haciendo que nuestros contratos sean más intuitivos, legibles y menos propensos a errores. En este artículo, exploraremos en profundidad qué son, cómo funcionan y cómo puedes combinarlos para escribir código de nivel profesional.

- Un Vistazo a los Tipos de Datos en Solidity
- Profundizando en los `Structs` de Solidity
- Entendiendo los `Enums` (Enumeraciones)
- La Sinergia Perfecta: Usando `Enums` dentro de `Structs`
- Almacenamiento Avanzado: `Structs` y `Mappings`
- Tabla Comparativa: `Structs` vs. `Enums` vs. `Mappings`
- Preguntas Frecuentes (FAQ)
- Conclusión
Un Vistazo a los Tipos de Datos en Solidity
Antes de sumergirnos en la creación de nuestros propios tipos, es útil recordar los bloques de construcción básicos que Solidity nos proporciona. Estos tipos de datos primitivos son la base sobre la cual construiremos estructuras más complejas:
- string: Cadenas de texto.
- int / uint: Enteros con y sin signo (el `uint` es una abreviatura de `uint256` por defecto).
- bool: Valores booleanos, que solo pueden ser `true` o `false`.
- address: Una dirección de Ethereum de 20 bytes.
- bytes: Arreglos de bytes de tamaño fijo o dinámico.
Si bien estos tipos son esenciales, a menudo necesitamos agruparlos para representar conceptos del mundo real, como un usuario, un producto o un envío. Aquí es donde los `structs` entran en juego.
Profundizando en los `Structs` de Solidity
¿Qué es un `Struct`?
Un `struct` es un tipo de dato definido por el usuario que permite agrupar múltiples variables de diferentes tipos bajo un solo nombre. Piénsalo como una plantilla o un molde para crear objetos de datos. En lugar de manejar un conjunto de variables sueltas, puedes encapsularlas en una estructura lógica, lo que hace que tu código sea mucho más organizado y fácil de entender.
Por ejemplo, si quisiéramos representar un envío en un contrato de logística, podríamos tener variables separadas para el paquete, el estado, el remitente, etc. Con un `struct`, podemos agrupar toda esta información:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract EjemploStructs { struct Envio { string paquete; string estadoActual; address remitente; address destinatario; uint precio; bool entregado; } }
Declaración y Creación de Variables `Struct`
Una vez que hemos definido nuestro `struct`, podemos crear variables de ese tipo. Hay dos formas principales de inicializar una variable `struct`:
- Por orden posicional: Asignas los valores en el mismo orden en que se declararon en la estructura. Este método es rápido pero puede ser propenso a errores si el orden cambia.
- Por nombre de clave: Asignas los valores especificando el nombre de cada campo. Este método es más legible y seguro, ya que el orden no importa.
contract EjemploStructs { struct Envio { string paquete; string estadoActual; address remitente; address destinatario; uint precio; bool entregado; } // Creación posicional Envio public miEnvioPosicional = Envio( "Laptop Gamer", "En tránsito", 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB, 1500, false ); // Creación por nombre de clave (recomendado) Envio public miEnvioPorNombre = Envio({ paquete: "Teclado Mecánico", precio: 120, remitente: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, destinatario: 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB, estadoActual: "Preparando", entregado: false }); }
Actualización de Datos en un `Struct`
Los datos dentro de una variable `struct` no son inmutables. Podemos modificarlos a través de funciones. Esto es crucial para reflejar cambios en el estado de nuestra aplicación. Por ejemplo, podríamos crear una función para actualizar los detalles de un envío.
function actualizarEnvio(string memory _paquete, string memory _estado) public { // Para este ejemplo, actualizamos miEnvioPorNombre miEnvioPorNombre.paquete = _paquete; miEnvioPorNombre.estadoActual = _estado; }
Entendiendo los `Enums` (Enumeraciones)
¿Qué es un `Enum`?
Un `enum` es otro tipo de dato definido por el usuario que sirve para crear un conjunto de constantes con nombre. Su propósito principal es restringir una variable para que solo pueda tomar uno de los valores de un conjunto predefinido. Esto mejora enormemente la legibilidad del código y evita errores lógicos.
Imagina que quieres representar el estado de un envío. Podrías usar un `uint`: 0 para 'Pendiente', 1 para 'Enviado', 2 para 'Recibido'. Pero, ¿qué pasa si alguien introduce el valor 5? Sería un estado inválido. Un `enum` resuelve esto.
Detrás de escena, los `enums` se representan como enteros sin signo, comenzando desde 0. El primer miembro es 0, el segundo es 1, y así sucesivamente.
contract EjemploEnums { enum EstadoEnvio { Pendiente, // Valor 0 Enviado, // Valor 1 Recibido, // Valor 2 Cancelado // Valor 3 } EstadoEnvio public estadoActual = EstadoEnvio.Pendiente; function marcarComoEnviado() public { estadoActual = EstadoEnvio.Enviado; } function obtenerEstadoUint() public view returns (uint) { return uint(estadoActual); // Retornará 1 si el estado es Enviado } }
La Sinergia Perfecta: Usando `Enums` dentro de `Structs`
La verdadera magia ocurre cuando combinamos estas dos herramientas. Podemos reemplazar campos ambiguos en nuestros `structs`, como un `bool` o un `uint` de estado, por un `enum` mucho más descriptivo. Volvamos a nuestro `struct Envio` y mejorémoslo.
contract ContratoLogistica { enum EstadoEnvio { Pendiente, Enviado, Recibido, Cancelado } struct Envio { string paquete; address remitente; address destinatario; uint precio; EstadoEnvio estado; // Reemplazamos el bool por nuestro enum } Envio public miEnvio = Envio( "Monitor 4K", 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB, 800, EstadoEnvio.Pendiente ); function actualizarEstado(EstadoEnvio _nuevoEstado) public { miEnvio.estado = _nuevoEstado; } }
Ahora, nuestro `struct` es mucho más claro. En lugar de un `true`/`false` que podría ser ambiguo, tenemos un campo `estado` que solo puede ser uno de nuestros valores predefinidos, eliminando la posibilidad de estados inválidos.

Almacenamiento Avanzado: `Structs` y `Mappings`
Para aplicaciones reales, rara vez tendremos una sola instancia de un `struct`. Lo más común es necesitar almacenar múltiples instancias, cada una asociada a una clave única, como la dirección de un usuario. Para esto, la combinación de `structs` y `mappings` es la solución ideal.
Un `mapping` es una estructura de datos de clave-valor. Podemos usar una dirección de Ethereum como clave y nuestro `struct Envio` como valor. Esto nos permite asociar un envío a cada usuario de manera eficiente.
contract ContratoLogisticaAvanzado { enum EstadoEnvio { Pendiente, Enviado, Recibido, Cancelado } struct Envio { string paquete; address destinatario; uint precio; EstadoEnvio estado; } mapping(address => Envio) public enviosPorRemitente; function crearOActualizarEnvio( string memory _paquete, address _destinatario, uint _precio ) public { // msg.sender es la dirección que llama a la función address remitente = msg.sender; // Creamos o actualizamos el envío asociado a la dirección del remitente enviosPorRemitente[remitente] = Envio( _paquete, _destinatario, _precio, EstadoEnvio.Pendiente ); } }
Con este patrón, cada usuario puede tener su propio `struct Envio` almacenado en el contrato, y podemos acceder a él o modificarlo de forma eficiente usando su dirección como clave.
Tabla Comparativa: `Structs` vs. `Enums` vs. `Mappings`
| Característica | Struct | Enum | Mapping |
|---|---|---|---|
| Propósito Principal | Agrupar variables de diferentes tipos en una sola entidad. | Definir un conjunto finito de constantes con nombre para representar estados. | Almacenar datos en un formato de clave-valor. |
| Estructura | Conjunto de campos con nombre y tipo. | Lista ordenada de identificadores. | `mapping(tipoClave => tipoValor)` |
| Ejemplo de Uso | Representar un usuario, un producto, una transacción. | Definir estados de un pedido (Pendiente, Enviado, Entregado). | Asociar el balance de un token a cada dirección de usuario. |
| Valor por Defecto | Una instancia donde cada miembro tiene su valor por defecto (0, false, etc.). | El primer miembro de la lista (valor 0). | Todas las claves posibles existen y apuntan al valor por defecto del tipo de valor. |
Preguntas Frecuentes (FAQ)
¿Cuál es el valor por defecto de un `uint` en Solidity?
El valor por defecto para un `uint` (y cualquier tipo entero) en Solidity es 0. Es crucial conocer los valores por defecto para evitar comportamientos inesperados: `bool` es `false`, `address` es `0x0000000000000000000000000000000000000000`, y `string` o `bytes` dinámicos son una cadena o arreglo vacío.
¿Por qué usar un `enum` en lugar de un `uint` para los estados?
Principalmente por tres razones: legibilidad (el código `estado = EstadoEnvio.Enviado` es mucho más claro que `estado = 1`), seguridad de tipos (el compilador se asegura de que solo asignes valores definidos en el `enum`) y mantenibilidad (es más fácil añadir o reordenar estados sin romper la lógica del contrato).
¿Se puede obtener la longitud de un `mapping` en Solidity?
No, los `mappings` en Solidity no tienen un concepto de "longitud" ni son iterables directamente. Esto se debe a su diseño de almacenamiento. Si necesitas llevar un registro del número de elementos, la práctica común es mantener una variable `uint` separada que se incrementa cada vez que se añade un nuevo elemento único.
¿Cuándo debería usar un `struct`?
Deberías usar un `struct` siempre que necesites agrupar datos relacionados que lógicamente pertenecen a una misma entidad. Esto no solo organiza tu código, sino que también puede ayudarte a evitar un error común en Solidity llamado "Stack too deep". Este error ocurre cuando tienes demasiadas variables locales en una función. Agruparlas en un `struct` puede resolver este problema y optimizar el uso del gas.
Conclusión
Dominar los `structs` y `enums` es un paso fundamental para pasar de escribir contratos simples a desarrollar aplicaciones descentralizadas complejas y robustas. Estas herramientas de Solidity nos permiten modelar datos del mundo real de una manera limpia, segura y eficiente. Al combinar `structs` para agrupar datos, `enums` para definir estados claros y `mappings` para un almacenamiento eficiente, tendrás en tus manos el conjunto de herramientas necesario para construir la próxima generación de smart contracts en la blockchain.
Si quieres conocer otros artículos parecidos a Structs y Enums en Solidity: Guía Completa puedes visitar la categoría Criptomonedas.
