Data Location Assignment [OLD]

The problem with the previous code was that it just saved the user struct copy in memory, and memory is deleted after the function has run. So after copying the user struct in memory and changing its balance, I added a line which uses this copy from memory to overwrite the user in the mapping (in storage) that belongs to this ID.

contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         User memory user = users[id];
         user.balance = balance;
         users[id] = user;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like
pragma solidity 0.5.1;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         User storage user = users[id];
         user.balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
2 Likes

I solved it by using the mapping variable directly instead of creating a new variable “user”.
pragma solidity 0.5.1;
contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {
     users[id].balance = balance;
     
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like

Here is my Solution! Is there any problem with the way I’ve written it?
It seems to work fine!

pragma solidity 0.5.1;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        User memory newUser;
        newUser.id = id;
        newUser.balance = balance;
        users[id] = newUser;
    }

    function updateBalance(uint id, uint balance) public {
        users[id].balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like

Hi @rostyslavdzhohola,

Great!.. and that’s the same method I used :slightly_smiling_face: except that I didn’t return the boolean in getBalance. That was a nice extra touch, as it confirms whether the zero balance is because the user’s balance is zero, or whether it’s because the user doesn’t exist :+1: However, do you think that it might be better to use another require() function in getBalance to revert the transaction and return an error message if getBalance is called with a user ID that doesn’t exist yet? i.e.

function getBalance(uint id) view public returns(uint) {
    require(users[id].isCreated, "User does not exist");
    return users[id].balance;
}

By the way, as isCreated is defined with the Boolean data type (bool) we don’t need to explicity state  === true  in require() . I think this is because the state variable itself will be either true or false anyway, which is what the first parameter in require() needs to evaluate to i.e.

// we can just write:
require(users[id].isCreated, "User does not exist");
// instead of
require(users[id].isCreated == true, "User does not exist");
1 Like

Hi @rostyslavdzhohola,

No, it assigns the new person to the mapping people in the following line:

people[creator] = newPerson; 

We don’t change anything in the Person struct. As I said before, we are using the struct like we do a class in JavaScript. Structs and classes are like templates or blueprints from which we can create new object instances to which we assign data on an instance by instance basis. We do that first in the createPerson function, then we pass newPerson (the new instance of Person) into insertPerson() which in turn assigns it to the mapping people. In the parameter  Person memory newPerson , Person acts like a data type, in the same way as uint or string or address . So in the same way that we can pass a string data type into a function, as follows:

string memory name

… we can also pass a new instance with the same data structure as the Person struct blueprint, as follows:

Person memory newPerson

The getPerson function’s ability to retrieve a specific person’s details is based on the mapping. As the mapping has been defined as follows…

mapping(address => Person) private people;

…each new person added will be stored as a key/value pair: the key being an address, and the value being an instance of the Person struct. So you will only be able to “getPerson” by their address, and not by their ID. In contrast, the mapping in the Data Location Assignment has been created to store key/value pairs with keys that have a uint data type. So, in order to be able to “getPerson” by ID in HelloWorld, you would need to create a new mapping whose keys also have a uint data type.

I would need to see where exactly in your code you’ve added uint id , to be able to say for certain why you are getting the first person created each time you call getPerson() . However, I hope this is enough to clarify things, but do let me know if anything is still unclear, and if you have any more questions.

2 Likes

Hi @VASKAS,

Good to see you back here in the forum again! :slightly_smiling_face:

Your solution is absolutely fine :ok_hand: In terms of the updateBalance function, it’s the most concise, and also uses less gas. So even though there are other equally valid alternatives, this is my preferred one.

1 Like

By changing in updateBalance memory to storage the change is persistent:
User memory user = users[id]; -> User storage user = users[id];

The reason for this is, memory does create a copy of the user which does only exist within the function and will be deleted after the function has been executed. In case of storage a reference is handed over to the actual user and the changes are made at the actual user.

1 Like

This is good to know. Thank you for pointing it out.

1 Like

Does mapping works as a pointer in C++?
Also, can I create a mapping for array of structs?

1 Like

I’m tagging my colleague @thecil so he can give you an answer on that, as he’s the C++ :mage:

I’m not sure I understand what you mean here. Why would you want an array of structs? If you need several structs in your contract (to act as templates for creating object instances with different property structures), then you’d want to define them all separately, each with their own name, and not store them within arrays. Also, arrays and mappings are two different ways of storing and manipulating multiple values. Mappings enable any individual value to be retrieved, immediately, when the value’s key is known: without the key, the value cannot be accessed. Arrays, on the other hand, can be iterated over to eventually find the value you are looking for, and so are more flexible than mappings, but are also less private and will use more gas.
So mappings and arrays are alternative data structures, and I can’t see why we’d want to map arrays. Unless you mean create a mapping for struct instances that already exist in an array, rather than create new ones…?

1 Like

since we want to maintain an accurate balance through the entire contract we switch memory to storage.

pragma solidity 0.5.1;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         User ***storage*** user = users[id];
         user.balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like

I want to clarify my understanding. If we create mapping with address pointing to a struct for example. If we use the same address/key and change the struct/value, than that key cannot point to multiple instances of struct/values. Previous struct/value gets lost, meaning it stays on a blockchain but our address/key points to the newest struct/value.
Does it make sense?

1 Like

When storage type variable is assigned to memory type it creates a new copy of data. If any update is done on copied data, it will not reflect in the original copy. So we need switch from storing in memory to storage.

pragma solidity 0.5.1;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         User storage user = users[id];
         user.balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like

Hello Filip… Just did the assignment. Took me some time and some wrestling with the problem but it ended up well… Wasn’t too sure where to start from, my mind always complicates so much, but it turned out to be very simple solution :smiley:

pragma solidity 0.5.1;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint newBalance) public returns(uint){
         users[id].balance = newBalance;
         return newBalance;
    }

    function getBalance(uint id)  public view returns (uint) {
        return users[id].balance;
    }

}
1 Like

//changed the reference users[id] that was created in the updateBalance function

pragma solidity 0.5.1;
contract MemoryAndStorage {

mapping(uint => User) users;

struct User{
    uint id;
    uint balance;
}

function addUser(uint id, uint balance) public {
    users[id] = User(id, balance);
}

function updateBalance(uint id, uint balance) public {
     User memory user = users[id];
     users[id].balance = balance;
}

function getBalance(uint id) view public returns (uint) {
    return users[id].balance;
}

}

1 Like

Hi @rostyslavdzhohola,

I think I understand what you are trying to clarify. What you have said is correct, for example:

struct Person {
   string name;
   uint balance;
}

mapping(address => Person) people;

The mapping has been created to store instances of the Person struct (values). When an instance is added to the mapping, what this effectively means is that the instance (together with its name and balance data) is “linked” to an address (its key). This address is what is subsequently needed in order to be able to delete this instance from the mapping, or to replace or modify its name and balance data. Just as you thought, a single address can only be the key to one Person instance. So, if we add the following new person to the mapping…

function addPerson(string memory name, uint balance) public {
   people[msg.sender] = Person(name, balance);
}

…if the same address were then to call this same addPerson function, but with a different name and balance, the original name and balance data (within the instance already “linked” to that address) would now be overwritten i.e. a second Person instance with a different name and balance would NOT be created, and the address used to make both function calls (each with different input data) would remain the key to only one Person instance.

I hope that now clarifies things even more for you :slight_smile:

1 Like

Hi Ivan,

Good to see you learning Solidity now :smiley:

Your solution is correct, except that we don’t want to return anything from the updateBalance function:

function updateBalance(uint id, uint newBalance) public /*returns(uint)*/ {
         users[id].balance = newBalance;
         // return newBalance;
}

At the moment this additional code doesn’t throw a compiler error because you aren’t actually doing anything with the return value, and so the additional code is redundant. We already have a getter which returns a user’s balance, anyway: both the original balance, and any that are subsequently updated.

2 Likes

Hi @Gagnon,

While your solution works, you will notice that the compiler generates a warning to say that the local variable:

User memory user = users[id];

is unused. This line of code can be removed completely, because by using…

users[id].balance = balance;

… you avoid the need for a local variable.

1 Like

I changed the location of user to be storage - remix even flagged a potential issue when I loaded the script.

pragma solidity 0.5.1;
contract MemoryAndStorage {

    mapping(uint => User) users;

    struct User{
        uint id;
        uint balance;
    }

    function addUser(uint id, uint balance) public {
        users[id] = User(id, balance);
    }

    function updateBalance(uint id, uint balance) public {
         User storage user = users[id]; // changed to storage - still same condition
         user.balance = balance;
    }

    function getBalance(uint id) view public returns (uint) {
        return users[id].balance;
    }

}
1 Like