1. Overview
assert and require are Scala’s preconditions used for writing programs using the Design-by-contract (DbC) approach. In this tutorial, we’ll learn about the functionalities of assert and require. Then we’ll see their differences in semantics and exception behavior and see which one to use in our programs.
Both assert and require are defined in the Predef.scala package. Thus, they’re automatically imported to all Scala programs.
2. assert
assert* is a precondition in Scala that evaluates a boolean expression as true or *false. It’s generally used to validate the execution of the program during runtime. We can see assert is used commonly in testing the functionality of programs.
assert throws AssertionError in case the expression evaluates false, indicating a fault in the executing program.
Let’s look at a function that calculates the square of a number:
private def squareNumCalculation(n: Int):Int = n * n
def getSquareOfNumber(n: Int):Int = {
assert(squareNumCalculation(2) == 4)
val squaredNum = squareNumCalculation(n)
return squaredNum
}
As we can see, the function squareNumCalculation calculates the square of an integer. In the public function, getSquareOfNumber uses assert to make sure the calculation is correct before returning the result to the calling program.
3. require
require is a precondition used to enforce certain preconditions before proceeding with the further execution of the program. It’s usually used to validate input parameters to a function, thus it enforces a precondition on the caller’s side.
Similar to assert, require also takes in an expression and evaluates the result as true or false. require passes the precondition in case of true, and it throws an IllegalArgumentException in case the result is false.
Let’s look at a function that issues driving licenses to applicants who are older than 18 years of age:
def issueDrivingLicense(name: String, age: Int): Unit = {
require(age >= 18)
println("Issued Driving License to %s ".format(name))
}
issueDrivingLicense("Darwin", 38) // prints "Issued Driving License to Darwin"
try {
issueDrivingLicense("Jr. Darwin", 5) // prints "Failed in require precondition"
} catch {
case e: IllegalArgumentException =>
println("Failed in require precondition-" + e.getMessage)
}
In our example function, the require precondition ensures that the age parameter is greater than or equal to 18. Otherwise, it throws an exception.
4. Functional Differences
Although assert and require look similar at a superficial level, there are several functional differences between them.
assert is commonly used to validate the code/program is working as expected. On the other hand, require is used to ensure that the program’s input parameters are valid. This difference is also evident in the behavior of exception thrown. assert throws an error (AssertionError) and require throws an exception (IllegalArgumentException).
The general difference between an error and an exception is applicable here as well. An error indicates an abnormal condition has occurred, and the execution of the program should stop. But exceptions are different.
We can catch exceptions in our applications and handle them gracefully, thus continuing with the program’s execution.
5. Technical Differences
So far, we’ve seen the functional differences between assert and require. Now, let’s look at the technical differences. If we look at the source code of the assert function in Predef.scala, we can see that the assert function definition is annotated with @elidable(ASSERTION).
Using this feature, we can disable assertions at the time of compilation by setting the compiler flags -Xelide-below ASSERTION or -Xdisable-assertions. Thus, the compiler will not include the assertions in the compiled bytecode, and therefore, we get a much lighter and faster executable.
A common packaging pattern includes all assertions in the test-build that runs all the assertions and makes sure the code is working perfectly. After that, we do the production build by excluding all the assertions. Hence, we get a faster and more performant program.
scalac -Xelide-below ASSERTION
or
scalac -Xdisable-assertions
require is not an elidable function. Since require emphasizes enforcing preconditions on the caller of a function, it makes more sense to make it non-elidable.
6. Conclusion
In this short tutorial, we’ve learned the functionalities of assert and require in Scala. Then we learned their semantics and exception behaviors with the help of simple examples. We use require when we need to enforce a precondition on the input parameters, and we use assert in places where we need to validate the execution of the program.
As usual, the full source code can be found over on GitHub.