In this article, we’ll start familiarizing ourselves with the order of evaluating expressions in Solidity. Plus, you’ll learn about multiple assignments (tuple assignments). Let’s go! πͺ
It’s part of our long-standing tradition to make this (and other) articles a faithful companion or a supplement to the official Solidity documentation.
Order of Evaluation of Expressions
Some articles ago, we learned about the order of precedence of operators, which may remind us of the order of evaluation. Feel free to check it out as well!
One important point is that the expressions are organized in a parent-child structure called an expression tree.
π² It’s a common tree-like structure made of nodes, where there’s a top-level node, also called the root node. A root node can have zero or more child nodes. When a child node itself contains child nodes, it’s simultaneously a parent node (but not the root node, because the root node has no parents). A node without any children is called a leaf node.
π Note: Tree structures in science draw inspiration from the real world: in human society, our parents are children of our grandparents, and we are the children of our parents. In biology, specifically in botany, there’s a tree trunk (the root node), then there are branches (the parent/child nodes) at various levels/depths (except for the last one), and finally, there are the leaves (leaf nodes).
With this knowledge in mind, and knowing that we’re working with an expression tree, we should be aware that the order in which the expression tree’s child nodes are evaluated is not strictly determined.
However, we can be sure that all parent node’s children are evaluated before the parent node. Expression trees have two more properties: the statements are executed in order, and short-circuiting is applied for boolean expressions.
Note: Short-circuit evaluation or minimal evaluation for boolean expressions implies that as soon as sufficient expression’s segment is evaluated, the evaluation terminates.
Let’s give an example: in an expression like ex = A or B or C or ... or Z
, as soon as the first operand, e.g., B
evaluates as true, the entire expression ex
evaluates as true. The same analogy goes for the and
operator, where the encounter of the first operand evaluating as false makes the whole expression evaluate as false.
Just to make things clear, the only working combinations for short-circuit evaluation, expressed in a form of (operator, operand_value)
, are (or, true)
and (and, false)
.
Destructuring Assignments and Assigning Multiple Values
A popular way of doing multiple assignments is by using tuple types.
A tuple type, or shorter, tuple, is a list of objects, not necessarily of the same types. The number of objects is constant at compile-time. This is an important feature because it enables validity checking and code optimizations, which can be further utilized later at runtime.
Tuples are commonly used for returning multiple values simultaneously and represent a practical approach. Tuples can be assigned to newly created variables, to pre-existing variables, or generally, to lvalues
.
π Note: “An lvalue
(locator value) represents an object that occupies some identifiable location in memory (i.e. has an address)” (source). As we now recognize, an lvalue
generally represents an object we can assign a value or an object to.
Tuples don’t exist as real, proper types in Solidity. They are just a syntactic grouping of expressions. In terms of our previous discussion, a tuple is a parent node, while its members are at least its leaves (if they’re primitive types).
Practical Example of Tuple Assignment
Let’s use a practical example to demonstrate what we’ve been talking about so far:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.5.0 <0.9.0;
Declares a simple contract with two functions, f()
and g()
, where f()
is a called function returning a tuple and g()
is a caller function receiving the tuple.
contract C { uint index;
f()
returns a fixed tuple (for demonstration purposes).
function f() public pure returns (uint, bool, uint) { return (7, true, 2); } function g() public {
Here we declare a receiving tuple with newly created variables. The number of element placeholders (separated by the ,
symbol) must match both lvalue
(left side) and value
(right side). Elements may be omitted as needed. Values 7 and 2 are assigned to variables x
and y
, respectively.
(uint x, , uint y) = f();
A commonly used trick for swapping values; we should note that it doesn’t work with non-value storage types.
(x, y) = (y, x);
As with the newly created variables above, we can also use an existing variable. Just mind the number of parameters (placeholders). This line sets the index variable to 7.
(index, , ) = f(); // Sets the index to 7 } }
Warning: we may not use tuples with newly created variables and existing variables; the two never mix: (x, uint y) = (1, 2);
Prior to Solidity v0.4.24 it was allowed to have an lvalue
tuple of smaller size.
In that case, the assignment would begin with the left or the right side, depending on the non-empty side of the tuple, e.g. (, y) = (1, 2, 3)
would produce y = 3;
in contrast (x, ) = (1, 2, 3)
would produce x = 1
, while the rest of the values were ignored.
After v0.4.24 it became mandatory to have the same number of elements (placeholders) on both sides.
Warning: We should be careful when assigning multiple reference-type values to tuples, because it may lead to unexpected copying behavior.
Conclusion
In this article, we started an interesting and important topic concerning the order of evaluation of expressions.
First, we explained how expressions are organized in expression trees. We also mentioned that expressions are evaluated level by level, but the order at each level is non-deterministic. Also, we found out what short-circuit (or minimal) evaluation is.
Second, we learned about tuples, destructuring assignments, and assigning multiple values using tuples. We also glanced at several different types of incomplete tuple assignments.
What’s Next?
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):