Mappings in Solidity

Mappings in Solidity

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 and value 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.