golang's fallthrough seems unexpected
Asked Answered
I

3

9

I have the following code:

package main

import (
    "fmt"
)

func main() {
    switch {
    case 1 == 1:
        fmt.Println("1 == 1")
        fallthrough
    case 2 == 1:
        fmt.Println("2 == 1")
    }
}

Which prints both lines on the go playground - see example here. I would have expected the fallthrough statement to include evaluation of the next case statement, but this seems not to be the case.

Of course, I can always use a bunch of if statements, so this is not a real impediment, but I am curious what the intention here is, since this seems to me to be a non-obvious result.

Anyone care to explain? For example: in this code, how can I get the 1st and 3rd cases to execute?

Implode answered 23/7, 2017 at 18:42 Comment(6)
Read the doc: golang.org/doc/effective_go.html#switch "There is no automatic fall through, but cases can be presented in comma-separated lists."Andantino
sure, but I want to do different things in each of those cases. and I used an explicit fallthrough. Seems like the fallthrough statement makes case 2==1 dead code.Implode
Right, that's a missing piece in the documentation.Andantino
@ScottStensland: The conditions are in the cases. This is completely valid.Mascarenas
what bothers me is that 2 == 1 is never true so should never be chosen ... this implies the fallthrough says to ignore the following case's test and just blindly switch into it even when its case test is false ... bug ? ... to me a followthrough should continue checking following case tests for next true case not simply jump into body of following case block skipping its testPermute
@ScottStensland yeah, that's exactly why I askedImplode
T
8

Switch is not a bunch of ifs. It's more akin to if {} else if {} construct, but with a couple of twists - namely break and fallthrough. It's not possible to make switch execute first and third cases - a switch does not check each condition, it finds first match and executes it. That's all.

It's primary purpose is to walk through a list of possible values and execute a different code for each value. In fact, in C (where switch statement came from) switch expression can only be of integral type and case values can only be constants that switch expression will be compared too. It's only relatively recently, languages started adding support for strings, boolean expressions etc in switch cases.

As to fallthrough logic it also comes from C. There is no fallthrough operator in C. In C execution falls through into next case (without checking case values) unless break operator encountered. The reason for this design is that sometimes you need to do something special and then do same steps as in another case. So, this design merely allows that. Unfortunately, it's rather rarely useful, so falling through by default was causing more trouble when programmer forgotten to put a break statement in, then actually helping when truly omitted that break intentionally. So, many modern languages change this logic to never fall through by default and to require explicit fallthrough statement if falling through is actually required.

Unfortunately, it's a it hard to come up with a non contrived example of fallthrough being useful that would be short enough to fit into an answer. As I said it's relatively rare. But sometimes you need to write code similar to this:

if x == a || x == b {
  if x == a {
    // do action a
  }
  // do action ab
} else if x == c {
   // do action c
} else if x == d {
  // do action d
}

In fact, I needed code of similar structure quite recently in one of my projects. So, I used switch statement instead. And it looked like this:

switch x {
  case a: // do action a
          fallthrough
  case b: // do action ab
  case c: // do action c
  case d: // do action d
}

And your switch from the question is functionally equivalent to this:

if 1 == 1 || 2 == 1 {
    if 1 == 1 {
        fmt.Println("1 == 1")
    }
    fmt.Println("2 == 1")
}
Tessitura answered 25/7, 2017 at 12:11 Comment(1)
exactly what i was looking for - thanks for the clear example.Implode
C
5

Presumably, Go's fallthrough behavior is modeled after C, which always worked like this. In C, switch statements are just shorthands for chains of conditional gotos, so your particular example would be compiled as if it was written like:

    # Pseudocode
    if 1 == 1 goto alpha
    if 2 == 1 goto beta
alpha:
    fmt.Println("1 == 1")
beta:
    fmt.Println("2 == 1")

As you can see, once execution enters the alpha case, it would just keep flowing down, ignoring the beta label (since labels by themselves don't really do anything). The conditional checks have already happened and won't happen again.

Hence, the non-intuitive nature of fallthrough switch statements is simply because switch statements are thinly veiled goto statements.

Cha answered 23/7, 2017 at 18:57 Comment(1)
sure, and go has goto statements as well, so I could write this - but it bothers me that the 2 == 1 seems like it's ignored.Implode
M
3

From the language spec:

A "fallthrough" statement transfers control to the first statement of the next case clause in an expression "switch" statement. It may be used only as the final non-empty statement in such a clause.

That seems to perfectly describe your observed behavior.

Mascarenas answered 23/7, 2017 at 18:56 Comment(1)
I don't disagree, and certainly there must be a reason for designing it that way - that's what I'm really asking. maybe the answer is "its different than a bunch of if's, and we already have that" but it would be cool to understand the type of case that makes this construct very useful.Implode

© 2022 - 2024 — McMap. All rights reserved.