Introduction to Solidity : Operators and Conversions

In this post we will learn about the operator and the conversion between types in Solidity. Let's start with operators

Operators

The arithmetic operators and bit operators can be applied to operands that doesn't have the same type. For exemple you can do y = x + z where x is a uint8 and z is a uint32.

Here the rules that are applied to define the type of the result of the operation :

  • If right operand can be implicitly converted to the type of the left operand. The result is of the type of the left operand.
  • If left operand can be implicitly converted to the type of the right operand. The result is of the type of the right operand.
  • If implicit conversion is not possible, the operation is not allowed.
int32 a = -10;
uint32 b = 5;
int32 c = a + b; // b will be implicitly converted to int32
uint128 a = 10;
uint256 b = 5;
uint256 c = a + b; // a will be implicitly converted to uint256

bytes32 a = 0x123;
uint b = 42;
bytes32 c = a + b; // This is not allowed, since a and b cannot be implicitly converted to each other

For operators like **, << and >> the result type it's always the type of the left operand.

Ternary operator

Ternary operator expression is used with this synthax :

<expression> ? <trueExpression> : <falseExpression>

In solidity numeric literals (such as 1.5) are treated as rational numbers and evaluated in unlimited precision. This means that expressions involving numeric literals can be evaluated to any precision required by the computation. For example, the expression 1.5 + 1.5 is a rational expression evaluated in unlimited precision, and the result of this expression is simply the rational number 3.

But the result of the ternary operator does not have a rational number type, even if all of its operands are rational number literals. The result type is determined from the types of the two operands.

That's why 1.5 + (true ? 1.5 : 2.5) is invalid. The first operand is a numeric literal, but the second one is a ternary operator that returns either 1.5 or 2.5 depending on the value of the boolean expression true. Since the result of this expression can be either 3 or 4, depending on the value of true, this imply a conversion of a fractional rational number to an integer, which is currently disallowed.

Compound and Increment/Decrement Operators

Here the list of compound and increment/decrement operators where x is a variable or something that can be assigned to.

  • x += y is equivalent to x = x + y
  • x -= y is equivalent to x = x - y
  • x *= y is equivalent to x = x * y
  • x /= y is equivalent to x = x / y
  • x %= y is equivalent to x = x % y
  • x &= y is equivalent to x = x & y
  • x |= y is equivalent to x = x | y
  • x ^= y is equivalent to x = x ^ y
  • x <<= y is equivalent to x = x << y
  • x >>= y is equivalent to x = x >> y
  • x++ is equivalent to x += 1
  • x-- is equivalent to x -= 1
  • ++x is equivalent to x += 1
  • --x is equivalent to x -= 1

The differences between x++ and ++x is that x++ returns the value of x before the increment, while ++x returns the value of x after the increment.

For example, if a is 5, then a++ will return 5 and then increment a to 6 and if a is 5, then ++a will increment a to 6 and then return 6.

delete

This operator can be used to set the initial value for a type. When used with integers, it assigns 0 to the variable.

For arrays, delete can create a dynamic array of length zero or a static array with the same length as the original array, and all elements are set to their initial value. If delete is used on an element of an array with index x, it will delete that specific element and leave all other elements and the length of the array untouched, creating a gap in the array.

If you want to remove items from an array, a mapping is a better option.

In the case of a Struct it will assigns a struct with all members reset. In short delete a is the same as a declared Struct without assignment, with the following caveat:

  • This has not effect on mappings, 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 a is a mapping, then delete a[x] will delete the value stored at x.

int a = 10;
delete a;
// After this statement, the value of 'a' will be 0

int arr[5] = {1, 2, 3, 4, 5};
delete arr;
// After this statement, 'arr' is a static array with length 5, and all elements are set to their initial value, i.e., 0

int arr[5] = {1, 2, 3, 4, 5};
delete arr[2];
// After this statement, 'arr' will be {1, 2, 0, 4, 5}

struct Person {
  string name;
  int age;
  bool isStudent;
};

Person p1 = {"John", 25, true};
delete p1;
// After this statement, 'p1' will be a struct with all members reset to their initial values, i.e., name="", age=0, isStudent=false

mapping (string => int) scores;
scores["Alice"] = 90;
scores["Bob"] = 80;
delete scores["Alice"];
// After this statement, 'scores' will be {"Bob": 80}

Conversions

Implicit Conversions

In Solidity, the compiler can automatically perform an implicit type conversion in certain cases, such as during assignments, when passing arguments to functions, and when applying operators. If a conversion is semantically meaningful and doesn't result in the loss of information, the conversion can occur between value-types. However, not all conversions are possible, such as int8 to uint256.

When an operator is applied to different types, the compiler attempts to implicitly convert one of the operands to the type of the other. The operations are performed in the type of one of the operands.

In the example below, y and z have different types, but uint8 can be converted to uint16 and not vice-versa.

As a result, y is converted to the type of z before the addition is executed, and the resulting type of the expression y + z is uint16.

After the addition, another implicit conversion is performed as it is assigned to a variable of type uint32.

uint8 y;
uint16 z;
uint32 x = y + z;

Explicit Conversions

While explicit type conversion is generally discouraged and can have unexpected results, there may be situations where it is necessary or useful. For example, when working with external interfaces or libraries that require a specific type, or when dealing with values that are too large or too small to fit into a certain type.

In such cases, explicit type conversion can be used to convert the value to the required type. However, it is important to be aware of the potential risks and to thoroughly test and validate the result to ensure that it is what you want and expect.

int  y = -3;
uint x = uint(y);

This code is allowed but the result will be a compilation error because you are trying to convert a negative value (-3) to an unsigned integer (uint), which can only hold non-negative values. In other words, the conversion from a signed integer to an unsigned integer is not possible if the signed integer has a negative value.

Conclusion

And this is it for operators and conversions. If you want to deep dive on this topics I recommend you to read the Solidity documentation. In the next article, we will talk about the units and global variables