Go: proposal: spec: select: multiple case expressions or fallthrough keyword

Created on 20 Dec 2017  ·  9Comments  ·  Source: golang/go

Unlike switch, select can only have one expression per case which may force code duplication or a separated function. This proposal is to add the option of multiple select case expressions.

select {
case <-cancel, <-w.(http.CloseNotifier).CloseNotify():
    // cancel action
}

Instead of:

select {
case <-cancel:
    // cancel action
case <-w.(http.CloseNotifier).CloseNotify():
    // cancel action
}

An example with assignment:

cancel := make(chan struct{})
cancelWork := make(chan int)
concurrentA(cancel)
concurrentB(cancelWork)
select {
case <-cancel, k := <-cancelWork:
    // unused variable assignments are set to zero value
    if k != 0 {
        // handle work
    }
    // cancel action
}

In more detail, the proposal is that multiple expressions for a select case is equivalent to individually cased expressions with a duplicated code block, plus the requirement of all assignments used in the single code block.

My use case is a web page where a browser close and a cancel button press cause the same cancel to occur in a concurrent request. In my case there are only two lines of code duplicated:

// https://github.com/pciet/wichess/blob/master/web_competitive15.go#L112
select {
case <-rdy:
    return // the client redirects to a GET /competitive15
case <-cancel:
    competitive15Matcher.Cancel(name)
    http.NotFound(w, r) // the client ignores the POST response
    return
case <-w.(http.CloseNotifier).CloseNotify():
    competitive15Matcher.Cancel(name)
    http.NotFound(w, r)
    return
}

The cases are different (CloseNotify doesn't need the http.NotFound) but the idea remains. Originally posted to golang-nuts: https://groups.google.com/forum/#!topic/golang-nuts/ROxbuskAglc

FrozenDueToAge Go2 LanguageChange Proposal

Most helpful comment

When used with sends on a channel, there is no way to tell which send succeeded in a case. When used with receives that happen to receive the zero value, there is no way to tell which receive succeeded in a case (particularly important since receives from a closed channel get the zero value). The same functionality can also be easily obtained with our current select by factoring out the case bodies.

All 9 comments

If you have case x := <-c1, y := <-c2, what happens?

Both variables would have to be used in the case block:

select {
case x := <-c1, y := <-c2:
    if x != false {
        // x non-zero value received
        fmt.Println(x)
        break
    }
    // y non-zero value received
    fmt.Println(y)
}

Comma ok is another case too:

select {
case x, ok := <-c1, y, ok := <-c2:
    // doesn't compile because of repeated ok var?
select {
case x, okX := <-c1, y, okY := <-c2:

Maybe since ok is always a bool it can have the same name:

select {
// still has a lot of commas
case var1, ok := <-c1, var2, ok := <-c2, var3, ok := <-c3:
    if ok == false {
        return
    }
    if var1 != 0 {
        fmt.Println(var1)
        break
    }
    if var2 != nil {
        fmt.Println(var2)
        break
    }
    fmt.Println(var3)
case <-c4, <-c5, <-c6:
case <-done:
    return
}

There's the problem of the zero-value being sent on the chan meaning the block can't figure out which var to use.

Your example use case could be written without duplication as:

select {
case <-rdy:
    return // the client redirects to a GET /competitive15
case <-cancel:
case <-w.(http.CloseNotifier).CloseNotify():
}
competitive15Matcher.Cancel(name)
http.NotFound(w, r) // the client ignores the POST response
return

That makes sense. I don't have a good example use case.

@mvdan despite the Go 2 tag do you think this could be a Go 1 feature? It doesn't seem like a breaking change.

There will be no more significant changes to the language before Go 2.0, so I would say very unlikely.

Our use case is similar to @pciet's example.

We have a event or tick case, both trigger the same behaviour.

We ended up moving that into a function. Felt bad to allocate resources on every function call.

I'll try @neild's solution and report if we land into some other problem.

A hypothetical case which wouldn't work this way:

eventAchan and ticker trigger one behaviour.

eventBchan, a closed channel and timeout trigger another behaviour.

fallthrough for switch is something I wasn't aware of. Adding this keyword to select could have the same effect. Here's an old golang-nuts thread about that: https://groups.google.com/forum/#!topic/golang-nuts/j0RobXhmGac

When used with sends on a channel, there is no way to tell which send succeeded in a case. When used with receives that happen to receive the zero value, there is no way to tell which receive succeeded in a case (particularly important since receives from a closed channel get the zero value). The same functionality can also be easily obtained with our current select by factoring out the case bodies.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

OneOfOne picture OneOfOne  ·  3Comments

natefinch picture natefinch  ·  3Comments

lkarlslund picture lkarlslund  ·  3Comments

myitcv picture myitcv  ·  3Comments

jayhuang75 picture jayhuang75  ·  3Comments