As you continue to develop smart contracts in Solidity, you will encounter situations where you need to store more complex data structures than simple variables and arrays. In this chapter, we will cover advanced data structures such as mappings, structs, and enums, which can be used to organize and manage your contract data more efficiently.
Mappings
Mappings are a data type in Solidity that allow you to store a value at a given key and retrieve it later. They are similar to hashes or dictionaries in other programming languages. Mappings are declared using the following syntax:
mapping(keyType => valueType) name;
For example, to declare a mapping that stores string values at address keys, you would write:
mapping(address => string) public userNames;
To set a value in a mapping, you can use the following syntax:
name[key] = value;
For example, to set a user’s name in the above mapping, you could do the following:
userNames[msg.sender] = "Alice";
To retrieve a value from a mapping, you can use the same syntax:
value = name[key];
For example, to get a user’s name from the above mapping, you could do the following:
string userName = userNames[msg.sender];
It’s important to note that mappings are not iterable, meaning you cannot loop through the keys or values in a mapping. If you need to iterate through a mapping, you can use a data structure like an array to store the keys and then iterate through that.
Structs
Structs are a data type in Solidity that allow you to group multiple variables together into a single compound data type. They are similar to classes or objects in other programming languages. Structs are declared using the following syntax:
struct Name {
type1 var1;
type2 var2;
...
}
For example, to declare a struct to store a user’s name and age, you could write:
struct User {
string name;
uint age;
}
To create a new instance of a struct, you can use the following syntax:
Name varName = Name(val1, val2, ...);
For example, to create a new User struct with the name “Alice” and age 25, you could do the following:
User alice = User("Alice", 25);
To access the variables in a struct, you can use the dot notation:
varName.var;
For example, to get the name of the above User struct, you could do the following:
string name = alice.name;
Enums
Enums are a great way to add meaning and context to your code by giving names to sets of related values. For example, you could use an enum to define the possible states of a task in a task management contract, like “TO_DO”, “IN_PROGRESS”, and “DONE”.
To use an enum in your code, you can declare a variable of the enum type and assign it one of the constants. For example, to use the Days enum from the previous example:
Days today = Days.Monday;
You can also use enums in function arguments and return types, as well as in control structures like if statements. For example:
function getDayString(Days day) public pure returns (string) {
if (day == Days.Monday) {
return "Monday";
} else if (day == Days.Tuesday) {
return "Tuesday";
}
// ...
}
Enums are also useful for defining constants in your contract. For example, you could use an enum to define the possible values for a contract’s state variable:
enum ContractState {
INITIALIZED,
ACTIVE,
PAUSED,
CLOSED
}
ContractState public state;
Conclusion
In conclusion, advanced data structures like mappings, structs, enums, and arrays are powerful tools for organizing and storing data in your Solidity contracts. By using these data structures, you can create complex and flexible contracts that can handle a wide range of scenarios. However, it’s important to use these tools carefully and consider their performance and gas costs when designing your contracts.
Exercises
To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.
Declare a mapping that stores uint values at address keys and assign it a name of your choosing.
mapping(address => uint) balances;
Create a new instance of the User struct from the example above and assign it a name of your choosing.
User myUser = User("Alice", 25);
Using the Days enum from the example above, declare a variable and assign it the value for Wednesday.
Days day = Days.Wednesday;
Write a function that takes a User struct as an argument and returns the user’s name as a string.
function getUserName(User user) public pure returns (string) {
return user.name;
}
Write a function that takes a ContractState enum as an argument and returns a string representation of the state. For example, if the input is ContractState.PAUSED, the function should return “Paused”.
function getContractStateString(ContractState state) public pure returns (string) {
if (state == ContractState.INITIALIZED) {
return "Initialized";
} else if (state == ContractState.ACTIVE) {
return "Active";
} else if (state == ContractState.PAUSED) {
return "Paused";
} else if (state == ContractState.CLOSED) {
return "Closed";
}
}