Table of contents
Introduction
In Solidity, mappings are a way to organize data. They work like a dictionary by assigning a 'key' to the data you want to store, known as the 'value', to make it easy to look up and retrieve the stored data.
This guide aims to provide an in-depth understanding of mappings in solidity. You will learn about its syntax and usage. Practical examples will be provided to illustrate its applications.
Prerequisites
To get a good understanding of this guide, it is required that you know the following basic concepts in Solidity programming:
Data Types
Variables
Functions
Data structures
Usage
Mappings are used to bind a key to a data value, this makes it easy to manage large amounts of data. It also provides a way to sort through a data pool since you only need the associated key to retrieve the data you need.
Syntax
The syntax for mapping in solidity is:
mapping(key => value) <visibility modifier> <name>;
key
andvalue
represent data types in Solidity.<visibility modifier>
specifies the visibility level of the mapping (public, private, external, or internal).<name>
is the keyword used to call the mapping within the contract.
Let's look at a simple storage contract:
pragma solidity >=0.7.0 <0.8.20;
contract StoreNumber {
// Mapping to store a number for each address
mapping(address => uint) storedNumber;
// Function to set a number for the caller's address
function setNumber(uint _number) public {
// Assign the input number to the caller's address in the mapping
storedNumber[msg.sender] = _number;
}
// Function to get the number stored for the caller's address
function getNumber() public view returns(uint) {
// Return the number stored for the caller's address in the mapping
return storedNumber[msg.sender];
}
}
The contract above is a simple storage contract where a user can store a number using the setNumber
function and retrieve the number using the getNumber
function. Here, mapping is used to bind the number stored to the address of the user who stored the number. Why? The setNumber
and getNumber
functions are both public functions, which means they can be called from outside the contract. Therefore, if a user sets a number by calling the setNumber
function, and their address is not bound to the number they set, another user calling the setNumber
function will change the number set. When the previous user calls the getNumber
function to retrieve their number, they will get the second user's number.
mapping(address => uint) storedNumber
is a mapping declaration named storedNumber
. It binds the number stored to the Ethereum address of the user storing the number. The Ethereum address is now used as the key to retrieve a number stored on the blockchain.
The addNumber
function takes one parameter: _number
. storedNumber[msg.sender] = _number
sets the key of the storedNumber
mapping as the address of the caller (msg.sender
), and the value it retrieves as _number
. Basically, it binds the number stored in _number
to the address of the caller. With this, different numbers can be stored in this contract, and it simply returns a number based on the address of the user who calls the getNumber
function.
The getNumber
function takes no parameters. return storedNumber[msg.sender]
returns the number bound to the address of the caller.
The StoreNumber
contract is deployed here.
Let's look at another example contract where you can retrieve a student's information using their registration number:
pragma solidity >=0.7.0 <0.8.20;
contract StudentRegistry {
//Define a structure for student registration
struct Student {
string name;
string department;
uint level;
}
//mapping to bind student information to their registration number as key
mapping(uint => Student) students;
//Function to add a new student to the school's records
function addStudent(uint regNo, string memory name, string memory department, uint level) public {
Student memory newStudent = Student(name, department, level);
students[regNo] = newStudent;
}
//Function to get a student's information from their registration number
function getStudent(uint regNo) public view returns(string memory name, string memory department, uint level) {
Student memory student = students[regNo];
return (student.name, student.department, student.level);
}
}
In the contract above, the data type assigned to the key is a struct. It binds the student's name, department and level to their registration number.
mapping(uint => Student) students
assigns the values in the Student
struct to the students
mapping, and sets the key as a uint
.
The addStudent
function takes four parameters: regNo
, name
, department
, and level
. Student memory newStudent = Student(name, department, level)
defines a new instance of the Student
struct named newStudent
, and assigns it the same values, that is, name, department, and level. students[regNo] = newStudent
stores the data for the newly created Student
instance (newStudent
), in the students
mapping, and assigns a key (regNo
) to it.
The getStudent
function takes one parameter: regNo
. Student memory student = students[regNo]
creates a local variable student
of type Student
, then assigns it the value retrieved from the students
mapping with the registration number provided.
return (student.name, student.department, student.level)
returns the name, department, and level associated with that registration number.
The StudentRegistry
contract is deployed here.
Nested Mappings
Nested mappings are mappings that are created within mappings. They allow data to be stored hierarchically.
Syntax
mapping(keyType => mapping(keytype => value)) <access specifier> <name>;
Let's look at an example contract where you can store and retrieve students' grades based on the student's Ethereum address, and then based on the course title.
pragma solidity >=0.0.0 <0.8.20;
contract StudentGrades {
mapping(address => mapping(string => uint)) grades;
// Function to set a student's grade for a particular course
function setGrade(address student, string memory courseTitle, uint grade) public {
grades[student][courseTitle] = grade;
}
// Function to get a student's grade for a particular course
function getGrade(address student, string memory courseTitle) public view returns (uint) {
return grades[student][courseTitle];
}
}
Let's take a look at the contract above. Firstly, mapping(address => mapping(string => uint)) grades
declares a nested mapping named grades
which binds a grade to the specified course, then binds the combination of the course and it's corresponding grade to a student's Ethereum address.
Next, the setGrade
function takes three parameters; address
, courseTitle
, and grade
. The course title acts as a key to retrieve the grade, while the student's Ethereum address acts as a key to retrieve both the course title and the grade. Basically, when this function is called, it sets the grade, binds it to the course specified, then binds both to the student's address.
Lastly, the getGrade
function takes two parameters; address
and courseTitle
It returns the grade associated with the Ethereum address and the course title specified.
The StudentGrades
contract is deployed here
Conclusion
Mappings in solidity are a powerful data structure that enable developers efficiently organize, update, and easily access data on the blockchain. They work by binding a unique key to a specific value, allowing smart contracts to easily store, retrieve, and update data.
In this guide, we covered the basics of mappings in solidity, its syntax, how to declare mappings, how to bind keys to different data types, and how to use nested mappings.
We provided examples of simple contracts where mappings are used to bind an Ethereum address to integers and structs, allowing secure and accurate data retrieval. We also demonstrated with the StudentsGrades contract, how nested mappings can be used to organize hierarchical data structures based on more than one key.
Mappings are an essential tool for managing data with smart contracts. With the knowledge gained from this article, developers can efficiently enhance data management and provide better functionality to users interacting with their smart contracts.