struct type allows you to organize related data of different types.
Objectives
By the end of this lesson you should be able to:- Construct a
struct(user-defined type) that contains several different data types - Declare members of the
structto maximize storage efficiency - Describe constraints related to the assignment of
structs depending on the types they contain
Creating a Struct
In the last exercise, we used amapping to create a relationship between an address and a uint. But what if your users have favorite colors too? Or favorite cars? You could create a mapping for each of these, but it would quickly get awkward. Instead, a struct can be used to create a custom type that can store all of a user’s favorites within one data type.
Create a new contract called Structs.
Setting up the Struct
Instantiate astruct with the keyword, followed by a name for the type, curly brackets, and the variables that make up the type. Add a stub for Favorites:
- Favorite number
- Birth Day of Month
- Favorite color
- Lucky Lottery numbers
uints for those variables. However, people don’t change their favorite number very often, and the day of the month that they were born on never changes.
Therefore, it’s probably more gas-efficient and less cumbersome to write other parts of the code, if we just use uint for both variables.
Favorite color can be a string.
For Lucky Lottery Numbers, we need a collection. We could use a dynamic array, since this will be in storage, but we already know that the lottery has 5 numbers.
Try to use this information to build the struct on your own. You should end up with something similar to:
Reveal code
Reveal code
Instantiating a Struct with Its Name
There are two ways to instantiate a struct using its name. The first is similar to instantiating a new object in JavaScript:Saving Multiple Instances to Storage
Next, we need to figure out the best way to organize theFavorites in storage. There are a few options, as always, each with tradeoffs. You could match the pattern you used for favorite numbers and utilize a mapping to match addresses to Favorites.
Another popular method is to use an array, which takes advantage of .push returning a reference to the newly added element, and the fact that the concept of undefined does not exist in Solidity.
First, instantiate an array of Favorites:
public function to add submitted favorites to the list. It should take each of the members as an argument. Then, assign each argument to the new element via the reference returned by push().
Reveal code
Reveal code
push it to storage.
Reveal code
Reveal code
Unexpected Behavior in Structs
Structs in Solidity exhibit some properties that are unexpected, or even frustrating. Working with them often includes untangling a set of mutually-exclusive properties and needs.Dynamic Storage Arrays in Structs
The product team has contacted you to let you know that the beta testers are complaining about thelotteryNumbers. As it turns out, not every locality has lotteries where 5 numbers are drawn. Some have 3, 4, or even 6!.
You might think this is an easy enough change. After all, you can just remove the size from the array declaration inside Favorites. Go ahead and try it:
memory method shown above.
push() to create an empty instance of Favorites, then assigning the values.
The reason this works is a little obtuse. In the failing example, an unsized uint array is the expected type for the argument, but a sized uint array is provided. Solidity cannot perform implicit conversions like this most of the time and you’ll get a compiler error if you provide the wrong type for an argument, even if it is convertible.
One exception to this rule is that Solidity can perform an implicit conversion during assignment if the variable on the right side “fits” into the variable on the left side.
uint[5] fits in uint[], so Solidity will allow it to sit 🐈.
But what happens if you use the getter for userFavorites to retrieve your entry?
Mappings Inside of Structs
You may addmappings inside of structs, subject to a few quirks and restrictions. Add mapping (uint => uint) numberPairs; to Favorites.
In addFavorites, assign newFavorite.numberPairs[33] = 66;
Deploy and test. So far, so good!
Déjà vu ahead: But what happens if you use the getter for userFavorites to retrieve your entry?
addFavorite function to return a reference to the new favorite?
mapping type cannot be returned by a public or external function, so neither can a struct that contains one.
newFavorite to a memory variable? Again, an error occurs because mappings can only be in storage.
Automatic Getters for Public Structs
As with other types, if you put apublic struct in storage at the contract level, the compiler will generate a getter automatically. However, these don’t work quite the way you might expect. For example, imagine:
myStruct will not be:
Conclusion
In this lesson, you’ve learned how to use thestruct keyword to create a custom type that stores related data. You’ve also learned three methods of instantiating them and common patterns for storing structs in storage. Finally, you’ve explored some of the constraints that emerge when working with more complex data types within a struct.