DEV Community

Cover image for The pitfall of implicit returns
Nicolas Fränkel
Nicolas Fränkel

Posted on • Originally published at blog.frankel.ch

15 2 2 2 2

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

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

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.

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay