The Pitfall of Implicit Returns
Implicit returns are a feature in some languages, but beware, concise code doesn't necessarily imply being better code. Learn more!
Join the DZone community and get the full member experience.
Join For FreeImplicit returns are a feature in some languages. They have recently bitten me, so here's my opinion.
Statements, Expressions, and Returns
Before diving into implicit returns, we must explain two programming concepts influencing them. A lot of literature is available on the subject, so I'll paraphrase one of the existing definitions:
An expression usually refers to a piece of code that can be evaluated to a value. In most programming languages, there are typically three different types of expressions: arithmetic, character, and logical.
A statement refers to a piece of code that executes a specific instruction or tells the computer to complete a task.
Here's a Kotlin snippet:
val y = 10 //1
val x = 2 //1
x + y //2
println(x) //1
- Statement, executes the assignment "task"
- Expression, evaluates to a value, e.g., 12
Functions may or may not return a value. When they do, they use the return
keyword in most programming languages. It's a statement that needs an expression.
In Kotlin, it translates to the following:
fun hello(who: String): String {
return "Hello $who"
}
In this regard, Kotlin is similar to other programming languages with C-like syntax.
Implicit Returns
A couple of programming languages add the idea of implicit returns: Kotlin, Rust, Scala, and Ruby are the ones I know about; each has different quirks.
I'm most familiar with Kotlin: you can omit the return
keyword when you switch the syntax from a block body to an expression body. With the latter, you can rewrite the above code as the following:
fun hello(who: String): String = "Hello $who"
Rust also allows implicit returns with a slightly different syntax.
fn hello(who: &str) -> String {
return "Hello ".to_owned() + who; //1
}
fn hello_implicit(who: &str) -> String {
"Hello ".to_owned() + who //2
}
- Explicit return
- Transform the statement in expression by removing the trailing semicolon — implicit return
Let's continue with Kotlin. The expression doesn't need to be a one-liner. You can use more complex expressions:
fun hello(who: String?): String =
if (who == null) "Hello world"
else "Hello $who"
The Pitfall
I was writing code lately, and I produced something akin to this snippet:
enum class Constant {
Foo, Bar, Baz
}
fun oops(constant: Constant): String = when (constant) {
Constant.Foo -> "Foo"
else -> {
if (constant == Constant.Bar) "Bar"
"Baz"
}
}
Can you spot the bug?
Let's use the function to make it clear:
fun main() {
println(oops(Constant.Foo))
println(oops(Constant.Bar))
println(oops(Constant.Baz))
}
The results are:
Foo Baz Baz
The explanation is relatively straightforward. if (constant == Constant.Bar) "Bar"
does nothing. The following line evaluates to "Bar"
; it implicitly returns the expression. To fix the bug, we need to add an else
to transform the block into an expression:
if (constant == Constant.Bar) "Bar"
else "Baz"
Note that for simpler expressions, the compiler is smart enough to abort with an error:
fun oops(constant: Constant): String =
if (constant == Constant.Bar) "Bar" //1
"Baz"
'if' must have both main and 'else' branches if used as an expression
Conclusion
Implicit return is a powerful syntactic sugar that allows for more concise code. However, concise code doesn't necessarily imply being better code. I firmly believe that explicit code is more maintainable in most situations.
In this case, I was tricked by my code!
Beware of implicit returns.
Go Further
Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments