If needs else branch in expression of type Unit


#1

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?


#2

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


#3

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.


#4

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


#5

#6

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.


#7

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?


#8

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


#9

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


#10

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.


#11

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.


#12

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.


#13

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.


#14

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.


#15

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.


#16

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.


#17

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.