Reference types refer to the memory location where the value is stored, rather than the value itself. Unlike value types, where the value of the data is stored directly in the variable, reference types store only a reference to the location of the data. Reference types hold the position of data in memory, allowing memory usage to be optimized regardless of the data’s size. Mapping types are data structures that store key-value pairs.
Arrays
The array type is a data structure that stores elements of the same type in sequence. Solidity has two types of arrays: fixed-length and variable-length. Variable-length arrays can add or remove elements, while fixed-length arrays have a set number of elements.
In the following example, ‘fruits’ is a variable-length array, and ‘fixedNumbers’ is a fixed-length array. Arrays can add or remove elements using functions like push() and pop().
string[] fruits = ["banana", "apple", "strawberry"]; // Variable-length
uint[3] fixedNumbers = [1, 2, 3]; // Fixed-length
function processSomething() {
fruits.pop(); // The last added element is removed with pop
fruits.push("peach"); // Variable-length arrays can add elements
uint length = fruits.length; // The number of elements can be obtained with length
uint secondElement = fixedNumbers[1]; // The i-th element can be accessed with array[i]
}
Structs
The ‘struct’ (structure) is used to collectively manage multiple variables of different types. A struct allows you to define your own data type, enabling a single variable to handle multiple values.
In the following example, a struct type called ‘Person’ is defined to manage information about a person who has multiple attributes. ‘Person’ has two variables, ‘name’ and ‘balance’, which are of different data types, and these are referred to as member variables. Two variables, ‘onePerson’ and ‘anotherPerson’, are defined as examples of the Person type, but note that each is initialized differently (both are valid methods of initialization).
struct Person {
string name;
uint256 balance;
}
Person onePerson = Person("Tanaka", 1e18); // 1e18 = 1000000000000000000 (18 zeros)
Person anotherPerson;
function processSomething() {
anotherPerson.name = "Sato";
anotherPerson.balance = 2e18;
}
Mapping Types
The mapping type is a data structure that stores key-value pairs. Values can be quickly retrieved (obtained) by specifying a key (think of it like a table in a database, with the key as the index).
In the following example, ‘addressToPerson’ is a mapping type variable that links the address type to the Person type (from the earlier struct example). It’s used to store a connection between an address and a person’s information. Lines 6 and 7 demonstrate how a mapping is recorded in this mapping.
mapping(address => Person) addressToPerson;
address oneAddress = 0x551af3f6A226a62BCb27f08B42401bd5629f2E44;
address anotherAddress = 0x0a011ABbCc1Ba8f82eF2c7490f8FD91cb35dBF62;
function processSomething() {
addressToPerson[oneAddress] = onePerson;
addressToPerson[anotherAddress] = anotherPerson;
}
Data Location
The ‘data location’ specifies where to store reference type data such as arrays and structures. The locations include storage, memory, and calldata.
When defining a variable, it’s necessary to consider one of these locations. The cost of gas varies depending on the data location used, in the order of storage > memory > calldata (with storage being the most expensive as it saves data permanently). Thus, gas cost must be considered in variable definitions.
※ Besides these, EVM also manages (has access to) areas such as the stack (a temporary area used during function execution), source code within, and the log area. For example, events mentioned in later chapters are stored in the log area.
Storage
Storage is a permanent data storage location stored in the smart contract’s storage area. Storage maintains state on the blockchain and ensures data persistence across transactions (the key difference from the other two is its persistence). When a variable is stored in storage, its value is maintained through each execution of the contract.
In the following example, a mapping variable named ‘addressToPerson’ is defined within a contract called ‘PersonStorage.’ This ‘addressToPerson’ is in storage.
contract PersonStorage {
...
mapping(address => Person) addressToPerson;
...
}
Memory
Memory is a temporary data storage location used during the execution of a function. Memory is discarded when the function execution ends, and the data disappears. Memory is used to hold dynamic data allocation, temporary variables, and local variables of a function, which are necessary during execution.
In the following example, an array called ‘numbers’ is defined as a function argument (to be discussed later), intended for use only within the function, so memory is appropriate. Since it’s memory, it’s possible to make changes to the values if needed.
contract PersonStorage {
...
function sumUp(uint[] memory numbers) {
uint sum = 0;
for (uint i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
...
}
}
Calldata
Calldata is a read-only data area where arguments passed to smart contracts from external sources and transaction data from external transactions are stored. Data in calldata cannot be modified.
In the next example, the argument ‘userAddress’ (the user’s address) is defined, and the balance of the user associated with that address is retrieved and stored in a variable called ‘balance’, which is of memory type.
contract PersonStorage {
...
function getBalanceOf(address[] calldata addressArray) {
uint256 sum = 0;
// addressArray[0] = 0x551af3f6A226a62BCb27f08B42401bd5629f2E44; // This is not allowed as it's read-only.
for (uint i = 0; i < addressArray.length; i++) {
sum += addressToPerson[addressArray[i]].balance;
}
...
}
}
Comments