Data Location Assignment

pragma solidity 0.7.5;
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
pragma solidity 0.8.4;

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

Nice solution @Dominykas_Pozleviciu :ok_hand:

… and your explanation is also correct :+1:
However, all local variables are lost when the function they are in finishes executing. To complete the explanation of why changing the data location of our local variable user from memory to storage saves the input balance to persistant storage in the users mapping, instead of being lost when the function has finished executing, we need to understand that this line…

User storage user = users[id];

… creates a pointer (a reference) to a specific User instance in the users mapping — the one which has, as its key, the id parameter input into the function. This means that when balance is assigned to user.balance in the second line, it is effectively being assigned to users[id] in the mapping:

user.balance = balance;

All values held in state variables and mappings are automatically saved in persistant storage — we don’t need to add the keyword storage for this to happen. In our example, the storage variable which is permanently saved is the mapping users. Mappings are a type of variable. Marking the local user variable with storage doesn’t permanently save this variable. Instead, it creates a temporary “bridge” to the mapping during execution, so that the new balance can be saved permanently in the mapping, before this “bridge” is lost when the function finishes executing.

I hope that makes sense, because it can be difficult to wrap your head around at first :sweat_smile: Just let us know if anything is unclear, or if you have any further questions :slight_smile:

Hi @vasja,

Your solution has made the updateBalance function exactly the same as the addUser function.

While this does still work…

Let us know if anything is unclear, or if you have any questions :slight_smile:

1 Like
pragma solidity 0.7.5;
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

First I tried to set 2 global variables for User.id and User. balance so I could replace the function body of the updateBalance function, but that brought up compiler errors(User.id= User A; User.balance=User B set as Global Variable)

Then I tried to rewrite the upDateBalance Body and leaving out Memory+using the Variables directly from the struct, which didn’t work either:

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

The simplest of tricks did it: I just changed memory to storage. But I don’t think this solution is a good way due to higher gas fees:

pragma solidity 0.7.5;
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

Hi @Daniel_Haydn,

You got there in the end! :ok_hand:

What you tried to do there (and also later within the function body itself) doesn’t make sense because User is the name of the struct we have defined. This acts like our own bespoke data type, which is why we can define a local variable as:

User memory user;
//or
User storage user;
// variable's name:  user
// variable's data type: User

…or a mapping as:

mapping(uint => User) users;
// mapping name:  users
/* This mapping will hold instances of the User struct
   mapped to unsigned integer keys (ID numbers) */

So our mapping is already a type of persistant variable in the contract state (what you are thinking of as a global variable, but which in Solidity is called a state variable). If we are going to assign a new value to the mapping (update it), we do the following:

  • Reference its name users (not the data type User), and the key of the specific User instance we want to update: users[id]
  • Use dot notation to update a specific property of this specific User instance:
    users[id].balance   or   users[id].id.
  • Assign the new value to this property: users[id].balance = balance
    The first .balance is the property name, and the 2nd  = balance  is the input parameter (the new balance).

This is the correct way to do what I think you were trying to do with User.balance  outside the function, and with…

… within the function.

We don’t need to assign the user’s ID to their record, because this isn’t changing. We are only updating their balance. We need to input their id as a parameter in order to use it as the key to their instance in the mapping, as I’ve described above. So, not only is the syntax wrong here…

… but what you are trying to do is also unnecessary.

The important thing to understand is that when we are assigning a new value to an instance of a struct, we use the name of the variable where the instance is saved, and NOT the name of the struct upon which the instance is based. If our variable is a mapping, we also need the key the instance is mapped to. So the most concise solution to the assignment is to assign the new balance value directly to the mapping:

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

If we first define a local variable, then when we assign the new balance we assign it to the variable name (user in our example, not User). And as our local variable is defined to hold a data structure with properties (based on our User struct), we use dot notation, again, to specifiy the property we are updating. This results in your longer alternative solution:

function updateBalance(uint id, uint balance) public {
    
    User storage user = users[id];
 // local variable defined with the name:  user
    
    user.balance = balance;
 // new balance value assigned to balance property of local variable (user)
}

In actual fact, the gas fees for both of these solutions are more or less the same, because they both do exactly the same thing but with different code. This post explains how.

I hope you find this helpful and it makes things clearer. Just let us know if it doesn’t, or if you have any questions :slight_smile:

1 Like

Thank you for your detailed explanation! It is quite mind-bending I agree! Haha

1 Like

I think about the other solution:

users[id].balance = balance;

It is easier to understand because it does not have a new variable - user - like in first solution:

User storage user = users[id];  
user.balance = balance;

Because then we have 3 variables with quite similar names: User, user, and users. And then it gets more complicated to understand haha

1 Like

My lazy solution:
Just replace “memory” with “storage”:


pragma solidity 0.7.5;
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

Hey Dominykas,

Yes, I agree! The gas cost is also more or less the same for either method. Even though the code is different they both perform the same low-level operations. The usefulness and practicality of the method with the local storage variable only really becomes evident when you are assigning multiple values e.g.

pragma solidity 0.7.5;

contract TrainingRecord {

   mapping(uint => Person) private athletes;
    
   struct Person {
      uint id;
      string name;
      uint m400;      // Personal best for 400m (time in seconds)
      uint discus;    // Personal best (distance in meters)
      uint highJump;  // Personal best (height in centimeters)   
   }
    
   function addAthlete(uint _id, string memory _name) public {
      Person storage newMember = athletes[_id];
      newMember.id = _id;
      newMember.name = _name;
   }

   // Update an athlete's personal best (PB) in each discipline
   function updatePBs(uint id, uint time, uint distance, uint height) public {
      Person storage update = athletes[id];
      update.m400 = time;
      update.discus = distance;
      update.highJump = height;
   }
    
   function getPBs(uint id) public view returns(
      string memory name, uint m400, uint discus, uint highJump
   ) {
      return(
         athletes[id].name, athletes[id].m400,
         athletes[id].discus, athletes[id].highJump
      ); 
   }  
}

This is definitely true :wink: but as you can see from my example, you can easily avoid such similar identifiers with a bit of creativity. What is also common practice is using an underscore for the names of the input parameters. This makes things much clearer when we want to use the same name for our parameter as the property we are assigning it to.

1 Like

I included the keyword storage and used the “incremental additive operator.” It updated fine.

pragma solidity 0.7.5;
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]; //assign user at index "id" to User obj whose attributes permeate life of contract
         user.balance += balance;   //use addition assignment operator to incrementally add to user balance 
    }

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

}
1 Like

Nice solution @m_Robbie :ok_hand:

This is a good attempt at explaining what this line is doing… but what’s actually happening is a bit different…
It creates a pointer (a reference) called user , which points to (references) a specific User instance in the users mapping — the one which has, as its key, the id parameter input into the function. This means that when you add balance to user.balance in the second line, it is effectively being added to the value stored in the balance property of users[id] in the mapping:

user.balance += balance;

The local variable user creates a temporary “bridge” to the mapping during execution of the function, so that the amount (balance) can be added to a specific user’s balance stored persistently in the mapping, before this “bridge” is lost when the function finishes executing.

I hope that makes sense, because it can be difficult to wrap your head around at first :sweat_smile: Just let us know if anything is unclear, or if you have any questions :slight_smile:

The problem here is that in the ‘User’ variable, the ‘memory’ keyword only stores the id data during the execution of the updateBalance function. But once that execution stops, the memory is cleared out for the next execution (getBalance). Then, the solution is to replace the ‘memory’ keyword for the ‘storage’ permanent data keyword.

pragma solidity 0.7.5;
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

No, that was a really great explanation! I really appreciate it. And it’s not too abstract, as I’ve taken C++ and other OOPs, and was certified in Java a million years ago (Java 3, hahaha!). The corrections are very welcome!!

I need to stop being sloppy with comments, dig deeper into more efficient solutions and ask more questions. Thank you for taking the time again!

1 Like

Thank you! I remade your code like this:

pragma solidity 0.7.5;

contract TrainingRecord {

   mapping(uint => Person) private athletes;
    
   struct Person {
      uint id;
      string name;
      uint m400;      // Personal best for 400m (time in seconds)
      uint distance;    // Personal best (distance in meters)
      uint height;  // Personal best (height in centimeters)   
   }
    
   function addAthlete(uint id, string memory name, uint m400, uint distance, uint height) public {
     
     athletes[id] = Person(id, name, m400, distance, height);
   }

   // Update an athlete's personal best (PB) in each discipline
   function updatePBs(uint id, uint m400, uint distance, uint height) public {
     
      
      athletes[id].m400 = m400;
      athletes[id].distance = distance;
      athletes[id].height = height;
   }
    
   function getPBs(uint id) public view returns(
      string memory name, uint m400, uint distance, uint height
   ) {
      return(
         athletes[id].name, athletes[id].m400,
         athletes[id].distance, athletes[id].height
      ); 
   }  
}

However, it does not allow me to compile when I choose only 2 arguments in the addAthlete function, such as:

questionaboutarguments.PNG

It wants me to add all variables from the Person struct. Why is it like that? And maybe it’s the reason why you write code like this? Because it works like this. Or maybe because you create new variables like newMember, _id and _name, that’s why?:

  function addAthlete(uint _id, string memory _name) public {
      Person storage newMember = athletes[_id];
      newMember.id = _id;
      newMember.name = _name;
   }

Just wanted to make it as simple as possible haha

1 Like
pragma solidity 0.7.5;
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;
    }

}

Awkwardly I missed that I could use the function storage(I thought it’s just a set state for integers), I thought too complicated and figured that its worth a try to bypass the problem by handling a solidity struct like and JS object ^^.

My wrong line of thoughts was to bind the elements of the struct to a variable and rewrite the whole thing XD.

I also struggle a bit with mapping and love mixing up JS and Solidity(it seems like a mix of JS and C++)it feels like learning a new language with 3 different accents at the same time. The try: User.id=users[id] was basically just fiddling around to see if something reacts in a way I could understand and proceed from.

I was thinking the Gas fees might be lower since we have 1 less variable in storage…but I wouldn’t be surprised if I got something wrong here.

Thank you very much for the feedback. The corrections of my mistakes will help to improve my “skills”:slight_smile:

1 Like

Hey Dominykas,

That’s great that you’re experimenting by reworking the code — and your new version looks good :+1: You’ll learn loads that way!

function addAthlete(uint id, string memory name) public {
    athletes[id] = Person(id, name);  // => compiler error
}

Using the syntax  StructName()  to create a new instance, requires you to include all of the properties defined in the struct. That’s why I deliberately used an example where I only wanted to record a new team member’s name and ID when they joined — to show you how that can be done using a local storage variable. Their personal bests would then be added later, and then updated on a regular basis.

function addAthlete(uint id, string memory name) public {
    Person storage newMember = athletes[id];
    newMember.id = id;
    newMember.name = name;
}

However, you could get round this, by assigning zero values to the other properties (undefined and null don’t exist in Solidity):

function addAthlete(uint id, string memory name) public {
      athletes[id] = Person(id, name, 0, 0, 0);
}

The zero values for the different data types are:

uint       0
string     ""
bool       false
address    0x0000000000000000000000000000000000000000
// or
address    address(0)

Or we could just use the other method from the assignment — the one you preferred:

athletes[id].id = id;
athletes[id].name = name;

… although, as you can see, the repetitiveness of the identifiers could make this code harder to read and manipulate.

It’s good to learn alternative ways to code the same outcome, because it gives us more flexibility to adapt our code to different requirements, and allows us to choose which method we think is the best solution in different situations.

1 Like

Hi @alank,

Whether we use  =   to replace the exisiting balance with the new one, or  +=  to add the new balance to the existing one, this change will still be lost once the updateBalance function has finished executing, unless we make this change to the user’s balance stored in the mapping (in persistent storage). Your code adds the input value to the balance property of a local variable (user), which is only stored temporarily in memory.

Can you correct this?

If you need some help, have a look at some of the other students’ solutions, and any related feedback and comments, which have been posted, here, in this discussion topic. That should give you some good ideas. But, then, if you’re still stuck, or if you have any questions, let us know so we can help you :slight_smile: