In Solidity, understanding data locations (storage
, memory
, and calldata
) is crucial for both performance and ensuring that your code behaves as expected. Let’s break down what each of these data locations means, how assignments between them work, and how they behave for value types (like uint
, bool
) versus complex types (like arrays, structs).
Data Locations Overview
- Storage:
- This is where state variables are stored. Data in
storage
is persistent between function calls and lives on the blockchain.
- This is where state variables are stored. Data in
- Memory:
- This is a temporary data location. Data stored in
memory
only exists for the duration of the function call and is erased afterward. It’s cheaper thanstorage
but doesn’t persist after the function execution.
- This is a temporary data location. Data stored in
- Calldata:
- This is a non-modifiable data location that is used for function parameters that come from external function calls. It’s cheaper than
memory
and is used primarily for external function inputs.
- This is a non-modifiable data location that is used for function parameters that come from external function calls. It’s cheaper than
Semantics of Assignments Between Data Locations
- Assignments between storage and memory (or calldata):
- Creates an independent copy.
- Example: Assigning a memory array to a storage array will copy the contents from memory into a new storage location. The two arrays become independent, meaning changes to one do not affect the other.
contract Example { uint[] storageArray; function assignMemoryToStorage(uint[] memory memoryArray) public { storageArray = memoryArray; // Copies the array from memory to storage memoryArray[0] = 123; // Does not affect storageArray } }
- Assignments from memory to memory:
- Creates a reference.
- Example: Assigning one memory array to another does not create a new array but rather creates a reference to the same array. Changes made to one variable will be reflected in the other.
function memoryToMemory() public pure { uint[] memory arrayA = new uint[](3); uint[] memory arrayB = arrayA; // arrayB references the same data as arrayA arrayB[0] = 123; // arrayA[0] is also 123 because arrayA and arrayB are references to the same data }
- Assignments from storage to a local storage variable:
- Assigns a reference.
- Example: If you assign a state variable (which is in
storage
) to a local variable (alsostorage
), the local variable will reference the same data as the state variable. Changes to the local variable will affect the state variable.
contract Example { uint[] storageArray; function assignStorageToLocal() public { uint[] storage localArray = storageArray; // localArray is a reference to storageArray localArray.push(1); // storageArray is modified as well } }
- All other assignments to storage:
- Creates an independent copy.
- Example: Assigning data to a state variable from memory, or assigning to a member of a storage struct or array, creates a new copy rather than a reference. Even if the local variable is a reference, when assigning to its members, a new copy is made.
struct Data { uint value; } contract Example { Data storageData; function assignToStorageMember(uint newValue) public { Data storage localData = storageData; localData.value = newValue; // This is a direct modification, not a copy. // However, when assigning to storage from memory: Data memory tempData = Data(newValue); storageData = tempData; // Copies tempData into storageData } }
Summary
- Value Types (e.g.,
uint
,bool
): Direct assignments (e.g.,uint a = b;
) always copy the value. - Complex Types (e.g., arrays, structs): Assignments between different data locations often create copies, but assignments within the same location (like memory to memory or storage to storage) usually create references.
Understanding the distinction between copying and referencing is vital for writing correct and efficient Solidity code, especially when dealing with large data structures like arrays and structs.