Solidity — Arrays + Examples

YBM
Coinmonks
6 min readJul 11, 2022

--

Photo by Markus Spiske on Unsplash

In Solidity, arrays take on a similar structure and style as in JavaScript, but there are still some tricky language-specific details. This post serves as a primer on Solidity arrays, and attempts to summarize some of these features. As always, please let me know if I’ve gotten anything wrong.

Intro

Arrays in Solidity are a reference type, meaning that they reference existing data. This contrasts with a value type, which passes an independent copy of that value to be used. Therefore, this means that reference types can be modified through multiple different names (ie each of their references). This is similar to JavaScript reference types.

In the example below, z and y both reference x and therefore either can alter (the third element in) x when f() is called:

In Solidity, reference types are comprised of structs, arrays, and mappings, and are more complicated to use than basic value types (ints, bools, etc) because the data location must also be explicitly declared: either memory, storage, or calldata. The only exception is state variables, which are automatically assumed and can only be storage. A previous post explores some of these differences. We will only be exploring arrays here.

In general:

  • memory — has a lifetime limited to an external function call, is mutable, and scoped within a function (non-persistent, modifiable)
  • storage — has a lifetime limited to the lifetime of a contract it is contained in, is mutable, and is where all state variables are stored (persistent, mutable)
  • calldata — is similar to memory, is immutable, and is a special data location containing function arguments (non-persistent, non-modifiable)

New to trading? Try crypto trading bots or copy trading

Data Location Effects

The data location of an array (and other reference types) has implications on assignment behavior:

  • Assignments between storage and memory (or from calldata) always create an independent copy

In the example below, g(x) can be called internally (by calling f() externally) even though x is a storage array, and g accepts memory arrays — because the g(x) invocation hands over a separate, independent copy of x in memory.

  • Assignments from memory to memory only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data

In the example below, because both z and zz memory arrays reference the same underlying memory array (y), changes to one memory variable affect all others referencing the same data.

  • Assignments from storage to a local storage variable also only assign a reference

In the example below, a local storage variable z references x and is therefore able to update x’s value in the 0th index by calling f(). The private function h(y) can also update x, because again a reference to x is passed rather than a copy.

*Note that this example would not even compile if h were neither private nor internal, because in that case h’s function parameters would be required to be either memory or calldata — local storage variables can only be passed in a function as a reference to an existing storage variable, and not available for public functions. They are particularly useful 1) for manipulation in-place of a reference variable or 2) within library functions to access storage data.

  • All other assignments to storage always copy — including assignments to state variables, even if the local variable itself is just a reference

In the example below, assigning the state variable x to either memory or calldata arrays is allowed because the whole array is copied into storage. They are shown to be copies (rather than reference) because updating x[0] has no effect on either y or z.

Dynamic Size vs Fixed Size Arrays

Arrays can have a compile-time fixed size, or can have a dynamic size. With a fixed size k(ie 5 elements) and element type T(ie uint256), fixed size arrays are represented with T[k](ie uint256[5]), and dynamic size arrays with T[](ie uint256[]).

All memory arrays have fixed size — however, dynamic arrays can also depend on runtime parameters.

Arrays can have elements of any type, including mappings or structs, although the same type restrictions apply for these reference types — mappings can only be stored in storage data location.

Arrays can also be multi-dimensional. For example, uint[][5] memory x would create a memory array of 5 dynamic arrays of uint. Indices are zero-based as in JS, and access is in the opposite direction of the declaration, meaning that to access the third uint in the second dynamic array, use x[1][2].

As with other state variable types, assigning a state array as public automatically creates a getter in Solidity. However, a numeric index must also be included as a required parameter in that getter (otherwise, it would be unknown which element to “get”, as the whole array is not returned so as to avoid high gas costs):

  • therefore for an array uint[] public x, the third element would be accessed by calling: x(2)

Note that trying to access an array past its length causes a failing assertion.

Initializing Arrays

Dynamic memory arrays can be initialized using the new operator. However, although dynamic, memory arrays can not be resized — required sizes must be determined in advance, or completely copied into new memory arrays to make updates. Newly allocated arrays are always initialized with the type’s default value (ie 0 for a uint256):

To update these values, elements must be assigned individually:

Statically-sized (fixed size) memory arrays can be initialized by an array literal — a comma-separated list of one or more expressions, in the form[…]. An array literal is interpreted as a statically-sized memory array with length equal to the number of expressions. The base type of the array is the type of the first expression in the list such that the other expressions can be implicitly converted to it. Otherwise it throws a type error: Unable to deduce common type for array elements. Note that one of the elements in the list must be explicitly that type — it’s not enough to have a type that all elements can be implicitly converted to.

For example, the following is a uint8[3] memory because the type of 1 is explicitly uint8 and the remaining values in the list (2, 3) can be implicitly converted to uint8.

[1, 2, 3]

To create a uint[3] memory instead, one of the elements must be explicitly converted to uint:

The following would not be allowed, because bool is not implicitly convertible to uint.

Fixed-size memory arrays cannot be assigned to dynamically-sized memory arrays either:

Array Members

Arrays have members in similar ways to JavaScript, with a few exceptions:

  • length: array length, available to all array types
  • push() and push(x): both only available to storage arrays

With no parameters, it appends a zero-initialized element at the end of the array and returns a reference to that element.

With parameters, it appends to the end of the array and returns nothing.

  • pop(): also only available to storage arrays. It implicitly calls a delete on the removed element. Unlike JS, it does not return that element.

Array Slices

Connected portions of arrays can also be accessed as slices, using x[start:end]. The array values will be returned between x[start] to x[end-1] inclusive. Indexes must be uint256 type or implicitly convertible to it. Index access is not relative to the underlying array, but to the start of the slice (and is only available to dynamic calldata arrays).

Other Array Details

  • avoid dangling references: avoid leaving references to storage items in arrays that have been deleted or moved, for example popped off elements.
  • bytes and string as arrays: they are both special arrays (bytes is similar to bytes1[], but it is packed tightly in calldata and memory); string is equal to bytes but does not allow length or index access.

Hopefully that gave some good insight into Solidity arrays and some key differences with JavaScript. Thanks for reading!

References:

--

--

YBM
Coinmonks

blockchain, crypto, web3, software, Lakers, anything that interests me. Any views expressed are the personal views of the author.