Solidity Operators and ‘Delete’ Keyword

5/5 - (2 votes)
Solidity Operators and Delete Keyword

Throughout this article, we’ll take a dive into the topics of operators, specifically the ternary operator, compound and increment/decrement operators, and the delete keyword.

We’ll conclude the article with a table overviewing the operators in Solidity.

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.

Solidity Operators

Operators can be coarsely divided into two categories:

  • arithmetic and
  • bit operators.

We can use both of these operators even when the operands don’t work with the same data types, e.g. y = x + z, even if x is of uint8 type and z is of int32 type.

When such a case occurs, there’s a simple set of rules used in determining the type used for the operation computation, as well as the final type of the computation result. This set of rules is particularly crucial when dealing with overflows.

These rules are:

  1. If there’s a possibility of converting the right operand type to the left operand type, use the left operand type;
  2. If there’s a possibility of converting the left operand type to the right operand type, use the right operand type;
  3. in any other case, disallow the operation.

When one of the operands is a literal number, the mechanism will first convert it to its “mobile type”, i.e. the smallest (most compact) type that can hold the value (unsigned types of the same bit-width are treated as “smaller” than the signed types).

In case both operands are literal numbers, the mechanism computes the operation with arbitrary precision.

Furthermore, the operator’s result type corresponds to the type the operation is performed in, with an exception for comparison operators, which always produce a result of type bool, e.g. if the operation is performed in uint8, the operator’s result type will be uint8.

Exceptions to the rule are the operators ** (exponentiation), << (left shift operator), and >> (right shift operator) because they use the type of the left operand as a base type for determining both the operation type and the result type.

Ternary Operator

The ternary operator is an operator commonly present in popular programming languages in one form or the other and is expressed in Solidity with syntax:

<expression> ? <trueExpression> : <falseExpression>

The result of using the ternary operator depends on evaluating the <expression>, i.e. if the <expression> evaluates as true, the result follows from evaluating <trueExpression>, and if the <expression> evaluates as false, the result follows from evaluating <falseExpression>.

Ternary Operator Solidity

The ternary operator does not result in a rational number type, even when all of its operands are rational number literals. In fact, the types of the two operands determine the result type following the same set of rules, including conversion to their mobile types if needed, i.e. when one of the operands is a literal type.

One example showing the set of rules at work is 255 + (true ? 1 : 0), which will revert because of an arithmetic overflow.

Since (true ? 1 : 0) is of uint8 type, and following rule 1, the left operand is also of uint8 type, 255 + 1 = 256. The uint8 type can hold unsigned integers ranging from 0 up to 2**8 - 1, i.e. 255, so 256 is an overflowed result.

Another example is of a literal expression 1.5 + 1.5, which is valid because it’s a rational expression evaluated in unlimited, i.e. arbitrary precision.

However, 1.5 + (true ? 1.5 : 2.5) is not a valid expression, because as mentioned above, the result of the ternary operator doesn’t have a rational number type.

In other words, the ternary operator result would have to be converted to the only numeric type currently available, i.e. (unsigned) integer, while Solidity doesn’t currently support that kind of conversion.

Compound and Increment/Decrement Operators

When looking at an assignment expression, we can identify two important parts: the left side and the right side.

πŸ‘‰ The commonly used term for the assignable left side, i.e. a variable, an object, or some other type of a value container is Lvalue, and for the right side is Rvalue.

Compound Increment Decrement Operators Solidity

Let’s assume the name lv represents an Lvalue: then lv += e is a synonym for lv = lv + e.

Operators -=, *=, /=, %=, |=, &=, ^=, <<= and >>= are interpreted in the same way. All these operators are known as compound operators.

πŸ“’ Note: we should make a difference between this kind of expression in mathematics, where it represents an equation, and in Solidity and the majority of other programming languages based on object-oriented, procedural, or declarative paradigms, it represents an assignment.

Post-increment and post-decrement operators lv++ and lv-- correspond to lv += 1 and lv -= 1.

The prefix post- signifies that if a post-incremented or post-decremented variable lv is an Rvalue, i.e. it’s assigned to a variable var, the variable var will first get the current value of lv, and after that (hence post-) lv will change its value by 1.

Corresponding pre-increment and pre-decrement operators ++lv and --lv will first (hence pre-) change the value of lv by 1, and only then assign the changed value to var.

delete Keyword

Maybe not so frequently mentioned as some other constructs, the delete keyword is very useful.

Let’s choose a name for an unspecified type, e.g. var, and take a look at how delete operates on it.

delete var assigns the default value for the type to var.

If the type is an integer, value 0 is assigned to var.

If the type is a dynamic array, delete will assign 0 to its member property length; for a static array, it will set all elements to their default value.

delete var[x] will delete the item of index x and leave all other elements and the array length untouched, i.e. it leaves a gap in the array.

πŸ›‘ Warning: If we know our code design will require removing some items, the mapping data structure is recommended.

If var is of a struct type, it will assign a struct with all struct members reset to their default values. Generally speaking, delete behaves very similarly to declaring var without an assignment, but with an important warning.

πŸ›‘ Warning: “delete has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If var is a mapping, then delete var[x] will delete the value stored at x”. (docs)

Finally, we can conclude that the behavior of delete var corresponds to an assignment to var, because it stores a new object in var.

We can notice this behavior directly when var is a reference variable, because delete var will reset var, and not the value var previously referred to.

The following example nicely illustrates and confirms what we learned.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() public {
        uint x = data;
        delete x; // sets x to 0, does not affect data
        delete data; // sets data to 0, does not affect x
        uint[] storage y = dataArray;
        delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
        // y is affected which is an alias to the storage object
        // On the other hand: "delete y" is not valid, as assignments to local variables
        // referencing storage objects can only be made from existing storage objects.
        assert(y.length == 0);
    }
}

Order of Precedence of Operators

So far, we’ve seen many different operators in Solidity, and to make it practical, let’s list them in a table showing the order of operator precedence in order of evaluation.

Order of Precedence of Operators

Source: https://docs.soliditylang.org/en/v0.8.15/types.html#order-of-precedence-of-operators

πŸ“’ Note: order of operator precedence can be modified by using parentheses.

PrecedenceDescriptionOperator
1Postfix increment and decrement++, —
New expressionnew <typename>
Array subscription<array>[<index>]
Member access<object>.<member>
Function-like call<func>(<args...>)
Parentheses(<statement>)
2Prefix increment and decrement++, —
 Unary minus
 Unary operationsdelete
 Logical NOT!
 Bitwise NOT~
3Exponentiation**
4Multiplication, division and modulo*, /, %
5Addition and subtraction+, –
6Bitwise shift operators<<, >>
7Bitwise AND&
8Bitwise XOR^
9Bitwise OR|
10Inequality operators<, >, <=, >=
11Equality operators==, !=
12Logical AND&&
13Logical OR||
14Ternary operator<conditional> ? <if-true> : <if-false>
 Assignment operators=, |=, ^=, &=, <<=, >>=, +=, -=, *=, /=, %=
15Comma operator,

Conclusion

In this article, we learned about operators, specifically ternary operators, compound, and increment/decrement operators. We also discussed what the delete keyword does with popular variable types.

  • First, we divided the operators into two broad categories: arithmetic and bit operators. Then we learned about rules for determining the operation computation type and proceeded into details of the operand and result type determination.
  • Second, we discussed the ternary operator mechanics and its specifics through theory and a couple of examples.
  • Third, we sneaked on compound and increment/decrement operators and found out what they do
  • Fourth, we made a short analysis of the delete keyword and took a look at how it operates with several types, particularly complex types.
  • Fifth, we took a look at a table of operators in Solidity.

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):