Diferencias entre `memory` y `storage` en Solidity
Clase 28 de 28 • Ethereum Developer Program 1ª Edición
Contenido del curso
Solidity tiene 2 palabras reservadas para el almacenamiento de datos: memory y storage. Como desarrollador es importante que conozcas las diferencias entre ellas y cuando debes usar una u otra.
De acuerdo con la documentación de Solidity, estas dos palabras clave se utilizan para referenciar tipos de datos donde…
"Los tipos complejos, es decir, los tipos que no siempre caben en 256 bits, deben manejarse con más cuidado que los tipos de valor que ya hemos visto. Dado que copiarlos
puede ser bastante costoso, debemos pensar si queremos que se almacenen en memory -memoria- (que no es persistente) o en storage -almacenamiento-
(donde las variables de estado son preservadas).
…
Cada tipo complejo, es decir, matrices y estructuras, tiene una anotación adicional, la "ubicación de datos", sobre si se almacena en la memoria o en el almacenamiento."
Ahora es importante ver dónde la Ethereum Virtual Machine (EVM) almacena datos:
“La máquina virtual Ethereum tiene tres áreas donde puede almacenar elementos.
El primero es "storage" -almacenamiento-, donde residen todas las variables de estado del contrato. Cada contrato tiene su propio almacenamiento y es persistente entre llamadas de función y bastante costoso de usar.
El segundo es "memory" -memoria-, se utiliza para almacenar valores temporales. Se borra entre llamadas de funciones (externas) y es más barato de usar.
El tercero es “stack”, que se utiliza para contener pequeñas variables locales. Es de uso casi gratuito, pero solo puede contener una cantidad limitada de valores.”
Más importante,
“Si, por ejemplo, se pasan tales variables en llamadas a funciones, sus datos no se copian si pueden permanecer en la memoria o en el almacenamiento.”
Aquí es donde se vuelve confuso, pero ten presente los siguientes puntos:
storage y memory se utilizan para hacer referencia a datos en el almacenamiento y la memoria, respectivamente.memory a partir de una variable de referencia de storage, estamos copiando datos de la memoria al almacenamiento. No se crea ningún nuevo almacenamiento.storage a partir de una variable con referencia a memory, estamos copiando datos del almacenamiento a la memoria. Se asigna nueva memoria.storage se crea en función localmente mediante la búsqueda, simplemente hace referencia a los datos ya asignados en el almacenamiento. No se crea ningún nuevo almacenamiento.Para recapitular, volvamos a la documentación:
"Ubicación de datos forzada:
parámetros (no devueltos) de funciones externas: calldata
variables de estado: storage
Ubicación de datos predeterminada:
parámetros (devueltos) de funciones: memory
todas las otras variables locales: storage"
Podemos cambiar la ubicación de los datos solo para parámetros de funciones y variables locales en función. Cada vez que una referencia de storage se envía a memory, se realiza una copia y la modificación adicional en el objeto no se propaga de nuevo al estado del contrato. Datos con referencia memory solo se puede "asignar" a storage si los datos de la memoria se pueden copiar a una variable de estado preasignada.
Veamos algunos ejemplos para ilustrar mejor lo visto anteriormente.
Empecemos viendo el caso de algunos getters.
function getUsingStorage(uint _itemIdx) public // definir como non-view para calcular gas // view returns (uint) { Item storage item = items[_itemIdx]; return item.units; } function getUsingMemory(uint _itemIdx) public // definir como non-view para calcular gas // view returns (uint) { Item memory item = items[_itemIdx]; return item.units; }
Ambas funciones devuelven el mismo resultado, excepto que en getUsingMemory se crea una nueva variable y se usa más gas:
// getUsingStorage "gasUsed": 21849 // getUsingMemory "gasUsed": 22149
Por otro lado, para los setters:
function addItemUsingStorage(uint _itemIdx, uint _units) public // definir como non-view para calcular gas // view { Item storage item = items[_itemIdx]; item.units += _units; } function addItemUsingMemory(uint _itemIdx, uint _units) public // definir como non-view para calcular gas // view { Item memory item = items[_itemIdx]; item.units += _units; }
Solo addItemUsingStorage modifica la variable de estado (consumiendo más gas):
// addItemUsingStorage // `units` cambia en `items` "gasUsed": 27053 // addItemUsingMemory // `units` no cambia en `items` "gasUsed": 22287
Para concluir, los puntos más importantes son:
memory y storage especifica a qué ubicación de datos se refiere la variable.storage en una función. Cualquier variable con referencia a storage en una función siempre hace referencia a un dato preasignado en el almacenamiento del contrato (variable de estado). Cualquier mutación persiste después de la llamada a la función.memory solo se pueden crear en una función. Pueden ser tipos complejos instanciados recientemente como arrays o structs (por ejemplo, a través de new int[...]) o copiados desde una variable de storage.memory y, si la variable estuviera en storage, se crearía una copia y cualquier modificación no persistiría.💡 Para mayor referencia, visita el texto original “Ethereum Solidity: Memory vs Storage & When to Use Them”.