1. Overview

In Scala 3, significant indentation is a feature that’s enabled by default, streamlining our code for better readability. In this tutorial, we’ll see how this feature allows us to define code blocks using braces or relying solely on indentation.

2. Optional Braces

With significant indentation, we’re now able to leave some braces out. Furthermore, poorly implemented programs are flagged with warnings when using them or errors when leaving them out.

2.1. Embracing Significant Indentation: With and Without Braces

We can now write the same code with braces:

if (house == "Gryffindor") {
  println("Ten points to Gryffindor!")
  println("Well done, Harry!")

Or we could write it without them:

if (house == "Slytherin")
  println("Ten points to Slytherin!")
  println("Excellent, Draco!")

When using significant indentation, the compiler enforces a rule that helps us maintain well-structured code. Specifically, in a brace-delimited region, no statement can start to the left of the first statement right after the opening brace.

The compiler issues a warning on any code that breaks this rule:

if (house == "Hufflepuff") {
  println("Ten points to Hufflepuff!")
  println("Great job, Cedric!")

println("Oops!")  // Compiler warning: indented too far to the left
-- Warning: indentation.scala:6:4
6 |    println("Oops!") // Compiler warning: indented too far to the left
  |    ^
  |    Line is indented too far to the left, or a `}` is missing
1 warning found

These warnings are incredibly useful because they help us quickly identify and correct issues related to indentation, thereby reducing the likelihood of bugs and improving code readability.

2.2. Disabling Significant Indentation With -no-indent

Sometimes, we may disable significant indentation, such as when transitioning from Scala 2 or working on projects that follow a different coding style. The -no-indent compiler flag allows us to do just that.

By using -no-indent, we revert to the Scala 2 style, where braces are mandatory for defining code blocks:

if (character == "Luke Skywalker") { // This is now mandatory
  println("May the Force be with you.")  
  println("You're a Jedi, Luke.")  
} // This is also mandatory

When significant indentation is turned off, another rule comes into play. Some expressions, like if, allow us to have a code line in each branch, but the following line must have less indentation, or the compiler reports an error.

This means some code that used to work in Scala 2 will fail to compile in Scala 3.

For example, the following code works in Scala 2, printing “After If!” but fails with a compiler error in Scala 3:

val a = 0
if(a < 0)
	println("Inside If")
	println("After If!") // Compiler error in Scala 3: missing `{`

Disabling significant indentation can be helpful in specific scenarios, such as integrating Scala 3 code with a Scala 2 codebase.

However, it’s crucial to understand that turning off this feature can result in noisy builds due to the introduced warnings and errors. Therefore, we should consider the pros and cons before using the -no-indent flag.

2.3. Optional Braces for Class Definition

When defining a new class, trait, or object, we can omit braces using a colon (‘:‘). The following are now valid definitions in Scala 3:

trait A:
  def f: Int

class C(x: Int) extends A:
  def f = x

object O:
  def f = 3

enum Color:
  case Red, Green, Blue

new A:
  def f = 3

package p:
  def a = 1

package q:
  def b = 2

2.4. Special Treatment of case Clauses

Scala 3 also introduced some changes to the syntax of match/case clauses. Prior to Scala 3, when using pattern matching, we’d need to use braces:

import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"

But in Scala 3, we can now rewrite without braces:

import scala.util.Random

val x: Int = Random.nextInt(10)

x match
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"

2.5. The end Marker

One of the potential issues raised when discussing this new feature is that it can make the code more difficult to understand when a given block ends. To solve this problem, Scala 3 offers an optional end marker:

def largeMethod(...) =
  if ... then ...
    ... // a large block
  end if
  ... // more code
end largeMethod

The end marker consists of the keyword end together with a specifier token. The specifier token can be one of the following:

  • if
  • while
  • for
  • match
  • try
  • new
  • this
  • val
  • given
  • <identifiers>

3. Conclusion

In this article, we saw one of the many new features introduced in Scala 3, optional braces. This language feature allows replacing some usages of braces by following specific indentation rules.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.