You’ve also made a great attempt at an explanation
By assigning users[id] to a local storage variable, we create a local “pointer” called user.
This points to (references) users[id]===> the user record in the mapping (users) which is mapped to the id input into the function.
The user records are struct instances with the properties defined in the User struct, and the local “pointer” has these same properties — notice that it has also been declared with the data type User . Any value which is assigned to a property of the local “pointer” (user) is effectively updating that same property of a specific user record in the mapping …
The local variable user creates a temporary “bridge” to the mapping during execution of the function. This enables a specific user’s balance (stored persistently in the mapping) to be reassigned with the newbalance (input into the function), before this “bridge” is lost when the function finishes executing.
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;
// assuming if user is just a copy in memory of the users[id] then
users[id] = user;
}
function getBalance(uint id) view public returns (uint) {
return users[id].balance;
}
As you realised afterwards, the initial idea of this assignment is to update the existing balance by replacing it with the new one. Despite this, I actually think that it makes more sense to do what you have done, and add an amount to the existing balance using the addition assignment operator += (instead of the assignment operator = ). However, if you add to, instead of replacing, the existing balance, I think your code would be clearer and more readable if you also changed the name of the balance parameter to amount, because you are adding an amount to the balance, rather than replacing it with a new balance.
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;
}
}
I changed memory to storage so that the new data is persistent and I removed some of the code from the updateBalance function because there is already a mapping for “users” that we can utilise.
Nice solution and explanation @unwise_guy
… and good to see you back here in the forum after a while
That’s correct … and by doing it this way (assigning the new balance directly to the user instance in the mapping), there is no need to explicitly specify the data location as storage, because all state variables and mappings are automatically saved in persistent storage.
function updateBalance(uint id, uint balance) public {
User memory user = users[id];
user.balance = balance;
users[id].balance = user.balance;
}
The balance gets lost once the function execution is done. We didn’t really save anything on the user. A pretty simple solution is to get the users using the id and change the balance from that map.