Please help - How to populate array with structs

I can’t figure this out. I look at the code, and it makes sense to me. Like it looks like it should work but it just doesn’t.

In the code you can see i’ve created an array of Structs, then I populate that array with structs but there’s something about the way i’ve done it that doesn’t work. I’ve looked high and low on google and Youtube and it’s just not working. Please help

// MAIN IDEA: map simpson characters to $ value. Enter Simpson name and it will spit out a value

pragma solidity 0.7.5;

contract SimpsonsStruct{
    
    struct Simpsons{
    string name;
    string food;
    }
    
    Simpsons public Homer = Simpsons("Homer","Donuts");
    Simpsons public Marge = Simpsons("Marge","Cocktails");
    Simpsons public Bart = Simpsons("Bart","Ice Cream");
    
    Simpsons[] public SimpsonsIndex = [Homer,Marge,Bart];
    
    function selectYourSimpson(uint _pickASimpson) view public returns(string memory, string memory){
        
        if(_pickASimpson > SimpsonsIndex.length){
            return ("Sorry, but we don't have that many Simpsons yet.", "Maybe add more?" );
        }
        
        return (SimpsonsIndex[_pickASimpson].name,SimpsonsIndex[_pickASimpson].food);
    }
    
    function addASimpson(string memory _simpsonName, string memory _simpsonsFood) public{
        Simpsons memory newSimpson = Simpsons(_simpsonName, _simpsonsFood);
        
        SimpsonsIndex.push(newSimpson);
    } 
}
1 Like

This is the part of the code I need help with. I feel like I wrote this correctly but for some reason it’s not working.

Hi @obikodi,

  • Use a constructor to populate your dynamic array on deployment with the 3 Simpsons struct instances you want to start with.

  • Use a require statement to verify that the input argument _pickASimpson is a valid index number. This way, if the index number is invalid, revert will be triggered with your error message. Using an if-statement to return the error message won’t work, because if _pickASimpson is an invalid index number revert will be triggered anyway, meaning that the function won’t finish executing or return any values.

/*
   Simpsons public Homer = Simpsons("Homer","Donuts");
   Simpsons public Marge = Simpsons("Marge","Cocktails");
   Simpsons public Bart = Simpsons("Bart","Ice Cream");
*/
   constructor() {
       SimpsonsIndex.push(Simpsons("Homer","Donuts"));
       SimpsonsIndex.push(Simpsons("Marge","Cocktails"));
       SimpsonsIndex.push(Simpsons("Bart","Ice Cream"));
   }

// Simpsons[] public SimpsonsIndex = [Homer,Marge,Bart];
   Simpsons[] SimpsonsIndex;
    
   function selectYourSimpson(uint _pickASimpson) view public returns(string memory, string memory) {
       require(_pickASimpson < SimpsonsIndex.length, "Sorry, but we don't have that many Simpsons yet. Maybe add more?");
    /*
       if(_pickASimpson > SimpsonsIndex.length){
           return ("Sorry, but we don't have that many Simpsons yet.", "Maybe add more?" );
       }
    */
       return (SimpsonsIndex[_pickASimpson].name,SimpsonsIndex[_pickASimpson].food);
    }

Let me know if you have any questions :slight_smile:

2 Likes

Also, because you have created a separate getter (selectYourSimpson), you don’t need Solidity to create an automatic getter for the array. So, you can change the array’s visibility from public to either internal or private. State variables have internal visibility by default, and so the internal keyword is optional.

1 Like

If you eventually want to achieve this, you are probably going to need a mapping. Maybe something along the lines of the following …

struct Simpsons{
   string name;
   string food;
   uint price;
}

mapping(string => Simpsons) simpsons;

constructor() {
    simpsons["Homer"] = Simpsons("Homer", "Donuts", 10);
    simpsons["Marge"] = Simpsons("Marge", "Cocktails", 20);
    simpsons["Bart"] = Simpsons("Bart", "Ice Cream", 30);
}
1 Like

Hi Jon this is incredibly helpful as always.

One question I do have is why did my version not work? As in why did the way I attempted to populate the array not work?

Was it because the syntax for it was wrong? Or was it something else? E.g I needed to use a constructor for reason x.

I’m hoping your answer can give me a better understanding of why my logic to code it the way I did was wrong, and how to avoid it in the future.

1 Like

Hey Kodi,

Your logic wasn’t wrong, it’s just that the Solidity language as it currently is doesn’t (yet) support initialising a storage array of struct instances at declaration (which is what you were trying to do). We can only initialise such an array from within a function or a constructor. Because you want to deploy your contract with a pre-populated array, you need to use a constructor.

Your logical thinking itself isn’t wrong, because we can use your same logic to successfully initialise storage arrays of strings and unsigned integers, for example …

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.7.5;

contract SimpsonsStruct{
    
    struct Simpsons{
        string name;
        uint price;
    }
    
    Simpsons public Homer = Simpsons("Homer", 10);
    Simpsons public Marge = Simpsons("Marge", 20);
    Simpsons public Bart = Simpsons("Bart", 30);
    
    string[] simpsonsName = [Homer.name, Marge.name, Bart.name];
    uint[] simpsonsPrice = [Homer.price, Marge.price, Bart.price];

    function selectYourSimpson(uint _pickASimpson) view public returns(string memory, uint) {
        return (simpsonsName[_pickASimpson], simpsonsPrice[_pickASimpson]);
    }
     
}

And we can also use your same logic to successfully initialise a storage array of arrays, for example …

// SPDX-License-Identifier: UNLICENSED
pragma abicoder v2;
pragma solidity 0.7.5;

contract SimpsonsStruct{
    
    string[2] public Homer = ["Homer", "Donuts"];
    string[2] public Marge = ["Marge", "Cocktails"];
    string[2] public Bart = ["Bart", "Ice Cream"];
    
    string[2][] simpsonsIndex = [Homer, Marge, Bart];

    function selectYourSimpson(uint _pickASimpson) view public returns(string memory, string memory) {
        return (simpsonsIndex[_pickASimpson][0], simpsonsIndex[_pickASimpson][1]);
    }
}

Both of these alternatives work perfectly well, and the only difference is that the storage arrays we are initialising at declaration contain values other than struct instances.

Both of these alternatives are almost certainly not as suitable for what you want to achieve with your contract as arrays of struct instances, but they do demonstrate quite nicely what Solidity does support in terms of initialising storage arrays, and that the logic you applied does work when Solidity supports it.

As for why Solidity doesn’t support initialising storage arrays of struct instances at declaration, but does for storage arrays of other data types, I really couldn’t tell you. There have been certain other operations involving data structures that earlier versions of Solidity haven’t supported, but which it now does support. There are also other examples that you will come across which still aren’t supported. Maybe some future version of Solidity will eventually support initialising storage arrays of struct instances in the way you tried to do it… or maybe not … I really couldn’t say.

Anyway, I hope that’s helpful as well, and restores your confidence in your own logical approach to these sorts of tasks :slight_smile:

Thank you Jon for your explanation, this makes a lot of sense now. It looks like I need to do a bit more learning around constructors. From the course I only learned that constructors are for things that need to run one time. To be fair I didn’t really know what that meant. I’m using these self made projects as an opportunity to expand my understanding of areas within Solidity that I don’t fully grasp yet. Fortunately thanks to your help and this forum, gaining that understanding has been a lot easier. Thanks.

1 Like

You’re very welcome Kodi :slightly_smiling_face:

This a great way to improve your understanding and skills, and it’s well worth investing the time to do this before moving on with the next course :ok_hand:

That’s correct in that constructors only execute their code once when the contract is deployed. We usually use them to set the values for specific state variables on deployment. For example, we can set an address state variable with the deployer’s address (msg.sender), like we did in our Bank contract for the owner state variable. We can also define a constructor with parameters, the values for which can then be determined on deployment. For example, instead of “hard-wiring” the property values for your initial 3 Simpsons instances within the constructor itself, you could provide the flexibility to decide upon and input these values on deployment …

// SPDX-License-Identifier: UNLICENSED
pragma abicoder v2;
pragma solidity 0.7.5;

contract SimpsonsStruct {
    
    struct Simpsons {
        string name;
        string food;
    }

    constructor(string[3] memory names, string[3] memory food) {
        SimpsonsIndex.push(Simpsons(names[0], food[0]));
        SimpsonsIndex.push(Simpsons(names[1], food[1]));
        SimpsonsIndex.push(Simpsons(names[2], food[2]));
    }
    
    Simpsons[] SimpsonsIndex;

    // functions ...
}

With this code, when you come to deploy your contract in Remix, you will notice that next to the Deploy button there is also now an input field for the arguments that need to be passed to the constructor. If you click on the down-arrow, you will get two separate fields, one for each array argument. You would need to input two arrays of strings e.g.

NAMES: ["Homer", "Marge", "Bart"]
FOOD: ["Donuts", "Cocktails", "Ice Cream"]