If needs else branch in expression of type Unit

I just ran into a interesting situation. Normally when you use if ... else as an expression you can not emit the else branch and I understand why. But let’s look at this situation

enum SomeEnum {
  A,
  B 
}

fun foo(e: SomeEnum): Unit = when(e) {
    A -> {
      doSomething()
      if(someCondition) {
        doSomethingElseAsWell()
      }
    }
    B -> ...
}

Compiling this fails with this error

‘if’ must have both main and ‘else’ branches if used as an expression

I can fix it by adding an empty else branch, but this looks stupid. I could also not use an expression body for the function but in that case I loose the check that I am exhaustive with my when expression.

I wonder if this is something that should be changed, so that if an if-expression returns Unit it can omit the else branch. Any thoughts?

Live with block syntax in this case as your use isn’t as expression.

Agreed, but I would still like to have the compiler check that the when expression is exhaustive and I can not do that without using it as an expression.

I agree on that one, perhaps a worthy enhancement proposal.

1 Like

Thx I understand how expressions work. As you might have noticed I was one of the people replying to that topic and I think I have explained my reasons why I would like to see an exception in this case, especially when the function is declared to return Unit.

That’s why I fiund it strange to bring up again…
If we don’t want to change it in the topic, but we do want to change it overhere, what makes the difference?

The difference as I explained is that I’d like to have exhaustive when but don’t want to use it as an expression because I just return Unit in all cases

Why do you want the inconsistency of enforcing an exhaustive when while allowing a non-exhaustive if?

I want the ability to have an exhaustive when without using it as an expression, if that is not possible yes I want a non-exhaustive if in case of a Unit result. I personally think that Kotlin is a bit strict about expressions in case of a Unit result, because all branches lead to the same result anyways.

Would this be acceptable to you?

fun foo(e: SomeEnum): Unit = when(e) {
    A -> {
      doSomething()
      if(someCondition) {
        doSomethingElseAsWell()
      }
      Unit
    }
    B -> ...
}

That way you’re explicitly stating, yep, this block does return Unit and not whatever the if-statement returns.

2 Likes

Rochlau’s post makes sense. Kotlin treats if and when consistently: they’re both exhaustive as an expression and non-exhaustive as a statement. If you want ‘if’ to be non-exhaustive, then use it as a statement.

Not really. I state explicitly that I return Unit at the function header, so I don’t really see why I need to state it again. I know the possible workarounds, that’s what I’m not looking for.
Maybe I put more value onto exhaustive whens. IMO when should always be exhaustive and this is where my problem is. I want an exhaustive when but I don’t want to deal with stupid unnecessary code just to support that.


Kotlin will not change when to be exhaustive, because this would be a breaking change, therefor I argue for a special treatment of if in case of a Unit result.

I can’t because I want the when to be exhaustive although I could argue that I am using it as a statement and the compiler should infer the Unit statement at the end.
Also I am not sure but this might be possible after https://youtrack.jetbrains.com/issue/KT-19878.

I would just give into the else branch. Just say (if I’m not mistaken)
else Unit

Maybe exhaustive could be a modifier on when if we really can’t live with that though

Or if the required destination type is Unit, then Unit can be inferred from any non-expression statement.

I don’t think special treatment of if in such a special edge case is justified. Kotlin tries to push you toward immutability and reducing side effects, and a when expression with a Unit return type just screams “SIDE EFFECT”. Sure, a lot of the time you can’t avoid those cases, but I’m not sure the language should actively encourage them. I think having a little more verbosity in a case like this is quite acceptable, because it makes inadvertently creating side effects harder.

3 Likes

I stole the definition of

val Any.exhaust = Unit

from some blog to make Unit when statements exhaustive. It is a nice and usefull trick.

2 Likes

Interesting suggestion!
This would allow me to write something like

fun test() = if(condition) value

Seems usefull in some (rare) cases, but maybe a bit confusing.

Yeah, I can agree with that.

At the risk of re-opening an old wound^H^H^H^H^Hthread, it’s not always obvious when Kotlin is expecting an expression.⠀Here’s the skeleton of a case I just hit in Kotlin 1.4.21:

fun read(reader: Reader) {
    reader.use {
        if (someCondition) {
            someList.add(null)
        } else if (someOtherCondition) { // <-- ERROR here
            // …
        }
    }
}

The return type of use() is inferred from the contents (and NOT from that of the function, which can only be Unit).⠀And to make it especially non-obvious, add() returns a Boolean (though that’s easy to miss as it’s nearly always ignored).⠀So the compiler insists on every other branch returning a Boolean too…⠀(In fact, my case was even worse, as the last block had a nested if, and every branch of that needed to return a Boolean as well…)

The best workarounds seem to be to specify the type parameters (use<Reader, Unit> {), to append a Unit line to the first branch, or to append else Unit to the last.

If both the error and the workarounds are a little unexpected and tricky for a seasoned Kotlin dev, how baffling and off-putting must they be for the less-experienced?⠀Is there nothing that can be done to avoid these cases (without breaking existing valid code)?

In this specific case, an extension like this would probably work quite well:

val Any?.ignoreResult get() = Unit