DEV Community

Cover image for The pitfall of implicit returns
Nicolas Frankel
Nicolas Frankel

Posted on • Originally published at blog.frankel.ch

The pitfall of implicit returns

Implicit 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.

-- Expression vs. Statement

Here's a Kotlin snippet:

val y = 10                //1
val x = 2                 //1

x + y                     //2

println(x)                //1
Enter fullscreen mode Exit fullscreen mode
  1. Statement, executes the assignment "task"
  2. 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"
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode
  1. Explicit return
  2. 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"
Enter fullscreen mode Exit fullscreen mode

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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

The results are:

Foo
Baz
Baz
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
  1. '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:


Originally published at A Java Geek on March 17th, 2024

Top comments (3)

Collapse
 
sethcalebweeks profile image
Caleb Weeks

Arguably, implicit code would mean adding the else statement. But I agree with your sentiment. I first encountered implicit returns in Elixir, and I'm still getting used to them.

Generally, I like using expressions over statements, but sometimes you can make assertions at the top of a block which saves a lot of nesting further down using early returns. There are other ways of solving this issue, and I haven't heavily weighed the pros and cons yet.

Collapse
 
jhelberg_63 profile image
Joost

Good and important article. You present definitions of expression and statement, in that order. The kotlin snippet shows examples, but then you present a statement as 1 and 2 as expression; that is confusing, as the definition uses the reverse order. You can better reverse the order of the definitions.

Collapse
 
alexmario74 profile image
Mario Santini

That's a perfect example on how a small piece of code can easy became very complex.