Stopping a loop that has done its job
Many languages have a break keyword that ends a loop in the middle. Scala doesn't. In the simple function-based exercises of this chapter, you stop a loop by leaving the function that contains it with return. When Scala reaches return, the function ends immediately, and so does any loop inside it.
An example with a banana
This function prints fruits until it has printed a banana:
def printUntilBanana(fruits: Array[String]): Unit = {
for (fruit <- fruits) {
println(fruit)
if (fruit == "banana") {
return
}
}
}
printUntilBanana(Array("kivi", "orange", "banana", "apple", "pear"))
kivi
orange
banana
On the third pass fruit is "banana". The program prints it first, because println comes before the check. Then the if succeeds and return leaves the function. The two remaining fruits, apple and pear, are never visited.
The order of the two statements decides what is printed. If the check came first, the banana would not be printed. That is a common source of off-by-one results with early exits: decide whether the matching item should be processed before the loop stops or not.
The same loop with indices
Looping over indices works the same way. The index lets you print the position as well:
for (i <- fruits.indices) {
println("Index " + i + ": " + fruits(i))
if (fruits(i) == "banana") {
return
}
}
Index 0: kivi
Index 1: orange
Index 2: banana
What return does, and what to watch for
returnleaves the whole function. It stops the loop, but it also skips everything written after the loop. In the banana function the loop is the last thing, so nothing is lost. If you add a line such asprintln("done")after the loop, it prints when the loop runs out of fruits and doesn't print whenreturnfires.returnbelongs to a method. In your own programs, put the loop inside adef, as the examples do.- A warning from the compiler. A
forloop is a method call that takes your loop body as a function, soreturninside it leaves the outer method from inside that function. Scala 3.5 compiles this and prints a warning that such returns are no longer supported, pointing toscala.util.boundary. The code still runs and stops the loop as shown above.
The newer way: boundary
Scala 3 offers boundary and break for this job. You mark a block with boundary and leave it with break, optionally passing a value out:
import scala.util.boundary, boundary.break
def indexOfBanana(fruits: Array[String]): Int = boundary {
for (i <- fruits.indices) {
if (fruits(i) == "banana") break(i)
}
-1
}
Called with Array("a", "banana", "c") it returns 1, and with Array("a") it returns -1. The lesson uses plain return, which is simpler for a first look at early exits.
The exercise
The array lessons holds "variables", "conditionals", "arrays", "functions" and "loops", and lastFinishedLesson is "functions". The task is to print every lesson the student finished, which means everything up to and including the last finished one.
- Loop over the lessons. Write
for (lesson <- lessons). The arrow reads as "for each lesson in lessons". - Print the lesson. Use
println("Finished " + lesson). The+joins the two strings. - Stop at the last finished lesson. Add an
ifthat compareslessonwithlastFinishedLessonand doesreturnwhen they are equal. It goes after the print, so the last finished lesson is printed before the loop stops.
val lessons = Array("variables", "conditionals", "arrays", "functions", "loops")
val lastFinishedLesson = "functions"
for (lesson <- lessons) {
println("Finished " + lesson)
if (lesson == lastFinishedLesson) {
return
}
}
Finished variables
Finished conditionals
Finished arrays
Finished functions
The text Finished loops never appears, because the loop ends on the fourth pass. Comparing strings with == checks their contents in Scala, so lesson == lastFinishedLesson is true for equal text and not only for the same object.
Key takeaways
- Scala has no
breakstatement;returnends the function and with it the loop. - Put the check after the work when the matching item should be processed, and before it when it should be skipped.
- Code after the loop does not run when
returnfires inside it. - Scala 3 prefers
scala.util.boundarywithbreakfor leaving a loop from inside a function body.
