Learn Solidity: Variables (Part 3)

Reference types, which should explicitly specify the data location

wissal haji
Nov 19, 2020 · 7 min read
a row of red British telephone booths (call boxes) along a street
a row of red British telephone booths (call boxes) along a street
Photo by Nick Fewings on Unsplash

Welcome to another article in the Learn Solidity series. In the last article, we have seen how data location works and when you can use each of the three locations: memory, storage, and calldata.

In this article, we will continue our journey of learning about variables in Solidity. This time we will focus on reference types, which should explicitly specify the data location, as we mentioned in previous articles. We will also see how you can define mappings, enumerations, and constants.

Arrays

In Solidity we have two types of arrays: storage arrays and memory arrays.

Storage arrays

These arrays are declared as state variables and can have either fixed or dynamic length.

Storage arrays with dynamic length can be resized, which means they have access to the push() and pop() methods.

Memory arrays

These arrays are declared with memory as their data location. They can also have either fixed or dynamic length, but dynamically sized memory arrays can’t be resized (i.e., can’t call push() and pop() methods); the size of the array must be calculated in advance.

Dynamically sized memory arrays are declared using the new keyword as follows:

Type[] memory a = new Type[](size)

One more point to mention here is about when you use memory arrays and you write something like this:

uint256[] memory array;
array[0] = 1;

You won’t get any warnings, but you will end up with an invalid opcode since array will be pointing to the zero slot according to the description of layout in memory, which should never be written to. Remember always to initialize the array before using it so that you can get a valid address to use.

Array slices

Array slices can only be used with calldata arrays and are written as x[start:end]. The first element of the slice is x[start] and the last element is x[end - 1].

Both start and end are optional: start defaults to 0 and end defaults to the length of the array.

Special Dynamically Sized Arrays

  1. byte[] or bytes

These arrays can hold an arbitrary length of raw byte data. The difference between the two of them is that byte[] follows the rules of the array type, and as mentioned in this part of the documentation, elements in memory arrays in Solidity always occupy multiples of 32 bytes. This means if an element has less than a multiple of 32 bytes, it will be padded till it fits the necessary size.

In the case of the byte array, this will waste 31 bytes for each element, which is not the case for bytes or string. I shall remind you that reading or writing a word (32 bytes) from memory costs 3 gas, and that’s why it is recommended to use bytes instead of byte[].

2. string

string is a dynamic array of UTF-8 data. As opposed to other languages, string in Solidity does not provide functions to get the length of the string or to perform concatenation or comparison of two strings (need to use a library).
A string can be converted to a byte array using bytes(<string>). This will return the low-level bytes of the UTF-8 representation of the string.

Note: One character can be encoded to more than one byte, which implies that the length of the byte array is not necessarily the length of the string.

string literals

See this part of the documentation.

string vs. bytes

Most of the examples of the documentation use bytes32 instead of string, and they have also made it clear to use the value types bytes1 to bytes32 if the number of bytes of the string can be limited, since it’s much cheaper.

Structs

As in C and C++, structs allow you to define your own types, like the following:

struct Donation {
uint256 value;
uint256 date;
}

Once you have defined your struct, you can start using it as a state variable or in your functions.

In order to initialize a struct we have two ways:

  • Using positional arguments:
Donation donation = Donation(
msg.value,
block.timestamp
);
  • Using the keywords:
Donation donation = Donation({
value : msg.value,
date: block.timestamp
});

The second approach will avoid us having to remember the order of the members of the struct, so it might be more useful than the first one.

Members of the struct are accessed using a dot:

uint256 donationDate = myDonation.date;

“It is not possible for a struct to contain a member of its own type, although the struct itself can be the value type of a mapping member or it can contain a dynamically-sized array of its type. This restriction is necessary, as the size of the struct has to be finite.” — Solidity documentation

Mappings

You can think of mappings as a massive key/value store where every possible key exists and any value can be set or retrieved in one move with the key.

Mappings are declared as follows:

mapping( KeyType => ValueType) VariableName

The KeyType can be any built-in value type (the ones we saw in part 1), bytes or string, or any contract or enum type. The ValueType can be any type including mappings, arrays, and structs.

One important thing to mention here is that the only data location allowed for mapping variables is storage, which means you can only declare them as state variables, storage pointers, or parameters for library functions.

Enums

Enumerations will allow you to group related values under a custom type, as in the following example:

enum Color { green , blue, red }

Access to an enum value is done using the following syntax:

Color defaultColor = Color.green;

Note: Enums can also be declared on the file level, outside of contract or library definitions.

Constants and Immutable State Variables

State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile time, while for immutable, it can still be assigned at construction time.

The compiler does not reserve a storage slot for these variables, and every occurrence is replaced by the respective value.

Constants are declared using the keyword constant:

uint256 constant maxParticipants = 10;

For immutable state variables, they are declared using the keyword immutable:

contract C {
address immutable owner = msg.sender;
uint256 immutable maxBalance;

constructor(uint256 _maxBalance){
maxBalance = _maxbalance;
}
}

You can find more details about constants and immutable state variables in this section from the documentation.

Note: It is also possible to define constant variables at the file level.

The delete Keyword

One last thing I would like to add is the use of delete in Solidity.
It serves for setting a variable to its initial value, which means that this statement delete a will behave as follows:

  • For integers, it is equivalent to a = 0.
  • For arrays, it assigns a dynamic array of length zero or a static array of the same length with all elements set to their initial value.
  • delete a[x] deletes the item at index x of the array and leaves all other elements and the length of the array untouched. This especially means that it leaves a gap in the array.
  • For structs, it assigns a struct with all members reset.
  • delete has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown).

Practice Time: Simple Crud

In this exercise, we will create a contract for managing users.

Here are the instructions:

  • Create a new file and add a contract named Crud.
  • Create a struct named User that contains the id and the name of the user.
  • Add two state variables and make them public: 1) a dynamic array of users and 2) an id that’s going to be incremented each time we create a new user.

The next step is to create the crud functions, but since I didn’t introduce you to Solidity functions, I will give you the syntax for declaring a function. In the next article, we will have a detailed discussion about them:

function function_name(<param_type> <param_name>) <visibility> <state mutability> [returns(<return_type>)]{ ... }

The visibility can be either: public, private, internal, external.
The state mutability can be either: view, pure, payable.

Here is the description of the functions that you will create.

1. add

visibility: public
state mutability: empty

This function will take the name of the user as a parameter, create an instance of User with a new id (the id is auto-incremented each time we add a new user), and add the newly created user to the array.

2. read

visibility: public
state mutability: view

This function takes the id of the user to look for and returns the user name if found or reverts the transaction if not (more on exception handling later).

3. update

visibility: public
state mutability: empty

This function will take the id of the user and a new name, then update the corresponding user if found or revert the transaction if the user does not exist.

4. destroy

visibility: public
state mutability: empty

This function takes the id of the user to delete and removes it from the array if found or reverts the transaction if the user does not exist.

Hint: Since all the three last functions need to look up the user, you will need to create a private function that will take the id of the user and returns their index in the array if found, to avoid repeating the same code.

When you are done, you can find the solution here on GitHub.

This concludes our discussion of variables. Next time we will look at functions and how you can use them in Solidity, so stay tuned for the upcoming articles if you want to learn more.

Better Programming

Advice for programmers.

Sign up for The Best of Better Programming

By Better Programming

A weekly newsletter sent every Friday with the best articles we published that week. Code tutorials, advice, career opportunities, and more! Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Thanks to Zack Shapiro

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store