Control Structures in Ethereum

Listen, I love Ethereum, I think it’s cool as hell, I even wrote a book about it, what I really don’t like is the documentation ( that’s why I wrote the book 🤔 ?) for instance here’s all the documentation on control structures:

Most of the control structures from JavaScript are available in Solidity except for switch and goto. So there is: if, else, while, do, for, break, continue, return, ? :, with the usual semantics known from C or JavaScript.

I get it, most programmers don’t want to be told what a for loop is for the 200th time in their career, but I side on those that do want to see a minimal example on all these control structures, so here we are, let’s go one by one.

if else :

The hardest working control statement in the business if serves a conditional and can be used in contracts like so:

pragma solidity ^0.4.0;
contract Conditional {
 uint refVal = 10;
 function isGreaterEqual(uint testVal) view public returns( bool ){
if(testVal >= refVal){
return true;
}
}
 }
// isGreaterEqual(9)... false
// isGreaterEqual(10)... true
// isGreaterEqual(11)... true

you are more likely to encounter multiple conditions concatenated like so:

pragma solidity ^ 0.4.0;
contract Conditional {
uint refVal = 10;
uint limit = 20;
function isGreaterEqualUnderLimit(uint testVal) view public returns(bool) {
if (testVal >= refVal
&& testVal < limit) {
return true;
}
}
}
//isGreaterEqualUnderLimit(9)... false
//isGreaterEqualUnderLimit(10)... true
//isGreaterEqualUnderLimit(11)... true
//isGreaterEqualUnderLimit(20)... false

The compiler ( I am using remix to test these contracts ) seems to return false umprompted, but smart contracts I think need to be explicit and verbose to avoid any potential bugs or misunderstandings, so using else might be a better pattern:

pragma solidity ^ 0.4.0;
contract Conditional {
uint lower = 10;
uint upper = 20;
function inBetween(uint testVal) view public returns(bool) {
if (testVal > lower
&& testVal < upper) {
return true;
} else {
return false;
}

}
}
//inBetween(9)... false
//inBetween(10)... false
//inBetween(11)... true
//inBetween(19)... true
//inBetween(20)... false
//inBetween(21)... false

While:

While loops are powerful things but could also break your contract, let’s start with a seemingly innocent loop:

pragma solidity ^ 0.4.0;
contract Loops {
uint public testVal = 6;
function decrementer() public {
while (testVal >= 4 ){
testVal --;
}
}
}

// Running the example:
// testVal : 6
// decrementer()... testVal-- runs 3 times...
// testVal : 3

The main issue with while loops in Solidity is the use of gas, in the above example ( testVal- -) which decrements by one testVal runs 3 times, and each time it runs it consumes and adds a bit of gas to the transaction, you can see how a while loop that goes over 100’s or thousands of fields could run out of gas, so they are discouraged, I would limit it’s use to small iterators.

For

For loops are usually an alternative to while loops and handy for repetitive operations, the ubiquitous : for (i = 0; i < 100 ; i++) { //repeat stuff 100 times } for instance.

Here’s an example in solidity:

pragma solidity ^ 0.4.0;
contract Loops {

uint public testVal = 0;
uint[] testArray;

function iterator() public {
for (uint i=0; i < 4; i++){
testVal++;
testArray.push(i);
}
}
function getArray() view public returns(uint[]) {
return testArray;
}
}
After running iterator():
// testval: 4
// getArray(): [0,1,2,3]

Like while loops, these are gas intensive methods; how much ? the above loop costs~ 139,276 wei when i<100 it runs out of the allotted 300,000 wei , just keep it in mind.

Break

A break allows you to exit a loop at some point, usually when a condition is met and then execute the next set of instructions in a function…

pragma solidity ^ 0.4.0;
contract Loops {
uint public testVal = 0;
function accumulator() public {
while (testVal >= 0) {
testVal++;
if (testVal == 4) {
break;
}
}
testVal = testVal + 6;
}
}
// After running accumulator()...
// testval : 10

Notice where the next instruction appears after the break; if you were using a defined variable in a for loop you would place the next statement closer and completely change the outcome, for instance:

pragma solidity ^ 0.4.0;
contract Loops {
uint public testVal = 0;
function innerAccumulator() public {
for (uint i = 0; i < 5; i++) {
if (i == 4) {
break;
}
testVal = testVal + i;
}
}
}
// After running innerAccumulator()...
// testVal : 6
let's go step by step to understand it:
// when.. 
// i: 0... testVal = (0 + 0) = 0
// i: 1... testVal = (0 + 1) = 1
// i: 2... testVal = (1 + 2) = 3
// i: 3... testVal = (3 + 3) = 6
// i: 4... since i == 4, testVal remains the same ( 6 ) and the addition doesn't get to execute.

If you have any doubts or weird readouts, I recommend you run your loops line by line like we just did to figure out the correct behavior.

Continue

A close relative of break, continue jumps out of the loop and has a nuanced different behavior depending on the loop:

  • In a while loop, it jumps back to the condition.
  • In a for loop, it jumps to the update expression.

Don’t worry, it makes slightly more sense in example form:

pragma solidity ^ 0.4.0;
contract Loops {
uint public testVal = 0;
function skipValue() public {
uint counter = 0;
while (counter < 6) {
counter++;
if(counter == 4)
continue;

testVal++;
}
}
}
// After running skipValue()...
// testVal : 5
let's go step by step :
// when.. 
// counter: 0... testVal = (0 + 1) = 1
// counter: 1... testVal = (1 + 1) = 2
// counter: 2... testVal = (2 + 1) = 3
// counter: 3... testVal = (3 + 1) = 4
// counter: 4... Here the if statement kicks in and goes back to the while loop, no addition is made, but the process continues.
// counter: 5... testVal = (4 + 1) = 5 Our final value.
It is important to note where the counter is placed, if placed under the if conditional you got yourself an infinite loop !

Now let’s check it’s use in for loops:

pragma solidity ^ 0.4 .0;
contract Loops {
uint public testVal = 0;
function skipValue() public {
for (uint i = 0; i < 5; i++) {
if (i == 2) {
continue;
}

testVal = testVal + i;
}
}
}
// after running skipValue()...
// testVal: 8
// step by step
// when..
// i: 0... testVal = (0 + 0) = 0
// i: 1... testVal = (0 + 1) = 1
// the condition i++ is evaluated and the conditional is true, so the loop starts again...
// i: 3... testVal = (1 + 3) = 4
// i: 4... testVal = (4 + 4) = 8
Without the continue it would run like this:
// i: 0... testVal = (0 + 0) = 0
// i: 1... testVal = (0 + 1) = 1
// i: 2... testVal = (1 + 2) = 3
// i: 3... testVal = (3 + 3) = 6
// i: 4... testVal = (6 + 4) = 10

As seen, both have roughly the same effect of skipping a condition, but knowing how they behave in detail is important to avoid bugs.

Return

A return statement simply stops the function execution and returns a value or not, used as a control structure you could use it like so:

pragma solidity ^ 0.4.0;
contract ReturnContract {
uint public testVal = 0;
function returner() public {
testVal = 1;
return;
testVal = 2; // <- this won't happen
}
}

// After running returner..
// testVal: 1
// the second testVal assignment doesn't get to execute.

more often than not, you will see it used along with a returned value.

pragma solidity ^ 0.4.0;
contract ReturnContract {
function returner() public pure returns(uint) {
uint testVal = 1;
testVal = testVal + 3;
return testVal;
testVal = 8; // <- This will never execute.
}
}
// Calling returner(): 4
// Not only does return gives you back a value, everything after the return does get to execute.

Notice we need to provide both the keyword returns and the type returned uint , you can also specify a name returns(uint testVala)

Ternary Operator

Not to be confused with labels : which I belive don’t exists in solidity, you can think of it as a shorthand if else:in the following form: condition ? expr1 : expr2 , it is super useful and greatly in use, here’s a few common uses starting with a common conditional:

pragma solidity ^ 0.4.0;
contract Ternary {
uint public outputVal = 0;

function testVal(uint inputVal) public {
inputVal < 10 ? outputVal = 1 : outputVal = 2;
}
}

// in other words, if inputVal is less than 10, outputVal is 1, else it is 2...
// testVal(3)... outputVal: 1
// testVal(11)... outputVal: 2

You can also use it with return:

pragma solidity ^ 0.4.0;
contract Ternary {
function testVal(uint inputVal) public pure returns(uint) {
return inputVal < 10 ? 1 : 2;
}
}
//testVal(5): 1
//testVal(11): 2

And assign variables :

pragma solidity ^ 0.4.0;
contract Ternary {
function testVal(uint inputVal)public pure returns (uint){
uint innerVal = inputVal < 10 ? 1 : 8;
return innerVal + 2;
}
}
// testVal(7) : 3   
// testVal(12) : 10

And that’s it, I could probably go on about conditionals all day, but this will hopefully be enough to give you an overview of how to use them in your contracts and expand on the documentation.

Thanks for reading !

Keno

Shameless plug: If you are looking for an introduction to Ethereum, Solidity and Smart Contracts I just published an eBook and print Book on getting started:
https://www.amazon.com/dp/B078CQ8L7V

About the Author :

Born Eugenio Noyola Leon (Keno) I am a Designer,Web Developer/programmer, Artist and Inventor, currently living in Mexico City, you can find me at www.k3no.com