Structure

While arrays are meant to group similar data together, structures build complex types from regular ones.

What is it

A structure is both a way to declare a new type and a variable associated with such a type. For example, there is no type Fan in Solidity while this type can be useful. A fan is composed of its contact and its level of fan-ness.

Use

Declaration

You can declare a struct as follow

// declaration of the type `Point`
struct Point {
  uint x;
  uint y;
}

// declaration a public variable of type `Point`
Point public origin;

Amiral tip Structure names start with a capital letter and follow CamelCase convention

Exercise
Correct!
False!

Loading...

Declare Fan type. contact is an address and level is a uint. Also declare sampleFan, a public Fan`
pragma solidity ^0.4.24; contract SpaceMuffin { // Declare `Fan` type here address[] public fans; // and `sampleFan` here modifier withinBounds(uint index) { require(index < fans.length); _; } function like() public { fans.push(msg.sender); } function access(uint index) view public withinBounds(index) returns (address) { return fans[index]; } }

Hint

  • Don't forget to put an ; at the end of each line
  • Declare simpleFan as a public variable

Default

Actually, you can assign a default value to the struct type.

There are two ways to do it

// Order of your arguments matters
// wrongOrigin.x == 0 and wrongOrigin.y == 1
Point public wrongOrigin = Point(0, 1);

// A more descriptive way
// Order doesn't matter but helps consistency
Point public origin = Point({x: 0, y: 0});

If you omit to initialise a struct variable, it will be initialise to each type default value. In the case of Point, it would be {x: 0, y: 0}.

Exercise
Correct!
False!

Loading...

sampleFan should be initialised with msg.sender (you) and 1000 * 1000 (your level of fan-ness 🤩)
pragma solidity ^0.4.24; contract SpaceMuffin { struct Fan { address contact; uint level; } address[] public fans; Fan public sampleFan; modifier withinBounds(uint index) { require(index < fans.length); _; } function like() public { fans.push(msg.sender); } function access(uint index) view public withinBounds(index) returns (address) { return fans[index]; } }

Hint

  • You can either write 1000 * 1000 or 1000000, first version is more readable
  • While you can declare it both way, you should do it only in one

Members

Having a sample fan was the just a test. And it is conclusive! So your Spacecrypt armada would benefit from migrating its address[] fans array to a Fan[] fans one. address contact would represent the same information that is already stored, and uint level the number of time a fan has liked the one true SpaceMuffin.

Access to inner variable of a struct is done as you would access members for arrays.

Point internal point;

function getX() view public returns (uint) {
  // <variable>.<member>
  return point.x;
}

Setting a member of a struct variable is nearly identical, you can do it as follow

Point internal point;

function setX(uint x) public {
  // works like a normal variable
  point.x = x;
}

Exercise
Correct!
False!

Loading...

Complete updateFan function by increasing the level of i-th element of fans by 1
pragma solidity ^0.4.24; contract SpaceMuffin { struct Fan { address contact; uint level; } // Wow! Wild ARRAY_OF_STRUCT appears Fan[] public fans; function like() public { // I'm too kind, I wrote this for you fans.push(Fan({contact: msg.sender, level: 1})); } // don't care about out of bound indexes function updateFan(uint index) public { // update the index-th fan here } }

Hint

  • You will have to access and set the fan value
  • Even if not required, you can take a look at the code I've done above

Optimisations

Do you remember what uint stand for? It stands for unsigned integer on 256 bytes. It means it can store value up to 2^{256}-1. It's enormous.

You know people really love your SpaceMuffin but probably won't call updateFan function that many times.

And struct posseses a hidden ability: optimisation. If you put two uint128 within a struct, the associated variable will take the same space as a uint256 while if you declare both variable outside of the struct they will take twice this space (while it is not necessary).

Points won't go over the value of a uint128, so we can rewrite the struct

struct Point {
  uint128 x;
  uint128 y;
}

// 1 is both a uint and a uint128, no problem
Point public point = Point(1, 1);

Tuples

Because struct are user-defined types, the Solidity compiler is (not-yet) able to handle them fully across the code.

Structures works perfectly in internal and private functions. You are allowed to use and return them as usual.

However, when using public and external functions, you have to deal with tuples.

What is it

A tuple simply wraps n values, one for each variable of the associated structure.

Within a function, they work like

// (variables to declare) = tuple
(uint x, uint y) = (1, 2);

// now x = 1 and y = 2 have been declared

Struct and function

Let's take a look at the following example

struct Point {
  uint128 x;
  uint128 y;
}

Point private origin = Point(0, 0);

// function has to return a tuple because it is public
// this tuple matches the definition of the struct
function getOrigin() pure public returns (uint128, uint128) {
  return (origin.x, origin.y);
}

Exercise
Correct!
False!

Loading...

Rewrite access function
pragma solidity ^0.4.24; contract SpaceMuffin { struct Fan { address contact; uint level; } Fan[] public fans; modifier withinBounds(uint index) { require(index < fans.length); _; } function like() public { fans.push(Fan({contact: msg.sender, level: 1})); } function updateFan(uint index) public { // oops, I haven't taught you about the += operator fans[index].level += 1; } // update this function to match the new contract structure function access(uint index) view public withinBounds(index) returns (address) { return fans[index]; } }

Hint

  • access is a public function, so it can't return a Fan
  • The modifier is still valid

It looks better now, and you will make it even better in the next chapter.