Ethereum Solidity: Memory vs Storage & When to Use Them

Forest Fang
Coinmonks
5 min readMar 18, 2018

--

Someone asked me on Github whether we should use storage or memory keyword in the following simplified code snippet:

For getUsingStorage and getUsingMemory, I tried both storage and memory and my unit tests were able to pass in both cases. So what’s the difference between storage and memory anyway and when should we use them?

Discover and review best Ethereum development tools

According to Solidity documentation, these two keywords are used for Reference Types where

Complex types, i.e. types which do not always fit into 256 bits have to be handled more carefully than the value-types we have already seen. Since copying them can be quite expensive, we have to think about whether we want them to be stored in memory (which is not persisting) or storage(where the state variables are held).

Every complex type, i.e. arrays and structs, has an additional annotation, the “data location”, about whether it is stored in memory or in storage.

It is now important to look at where EVM (Ethereum Virtual Machine) stores data:

The Ethereum Virtual Machine has three areas where it can store items.

The first is “storage”, where all the contract state variables reside. Every contract has its own storage and it is persistent between function calls and quite expensive to use.

The second is “memory”, this is used to hold temporary values. It is erased between (external) function calls and is cheaper to use.

The third one is the stack, which is used to hold small local variables. It is almost free to use, but can only hold a limited amount of values.

Most importantly,

If you e.g. pass such variables in function calls, their data is not copied if it can stay in memory or stay in storage.

This is where it gets confusing:

  1. The storage and memory keywords are used to reference data in storage and memory respectively.
  2. Contract storage is pre-allocated during contract construction and cannot be created in function call. After all, it makes little sense to create new variable in storage in a function if it is to be persisted.
  3. Memory cannot be allocated during contract construction but rather created in function execution. Contract state variable is always declared in storage. Again, it makes little sense to have state variable that cannot persist.
  4. When assigning a memory referenced data to a storage referenced variable, we are copying data from memory to storage. No new storage is created.
  5. When assigning a storage reference data to a memory referenced variable, we are copying data from storage to memory. New memory is allocated.
  6. When a storage variable is created in function locally by look up, it simply reference data already allocated on Storage. No new storage is created.

To recap, refer back to the documentation:

Forced data location:

* parameters (not return) of external functions: calldata

* state variables: storage

Default data location:

* parameters (also return) of functions: memory

* all other local variables: storage

We can change data location only for parameters of functions and local variables in function. Whenever a storage reference is casted to memory, a copy is made, and further modification on the object does not propagate back to contract state. memory reference can only be “assigned” to storage reference if memory data can be copied to a pre-allocated state variable.

Back to our illustrative contract above, for getters:

Both functions return the same result, except in the getUsingMemory a new variable is created and resulting in more gas used:

On the other hand, for setters:

Only addItemUsingStorage modified the state variable (consuming more gas):

So to close, the takeaways are:

  1. memory and storage specifies which data location the variable refers to
  2. storage cannot be newly created in a function. Any storage referenced variable in a function always refers a piece of data pre-allocated on the contract storage (state variable). Any mutation persists after function call.
  3. memory can only be newly created in a function. It can either be newly instantiated complex types like array/struct (e.g. via new int[...]) or copied from a storage referenced variable.
  4. As references are passed internally through function parameters, remember they default to memory and if the variable was on storage it would create a copy and any modification would not persist.

--

--

Forest Fang
Coinmonks

functional programming advocate; visualization addict; blockchain enthusiast;