Go: proposal: Go 2: permit directional variants of named channel types

Created on 20 Sep 2017  路  12Comments  路  Source: golang/go

Consider this program:

package main

import (
    "fmt"
)

type Foo chan string

func main() {
    c := make(Foo, 1)
    c <- "gotcha"
    fmt.Println(<-c)
}

All is well. Now try to declare a direction for a Foo:

package main

type Foo chan string

func main() {
    var x <-Foo
}

// or

func x(c <-Foo) { }

This doesn't parse, but it's reasonable to expect it would and maybe, perhaps with parenthesization, easy to define without ambiguity. (A directional Foo with element type Foo could be tricky.)

Reported on twitter.

Go2 LanguageChange NeedsDecision Proposal

Most helpful comment

Definitively a language change, possibly backward compatible.

But why would we want to allow such a thing? It makes the direction arrow an operator on types, and only on channel types. And it only can make a bidirectional channel a read-only channel. What if I wanted to create a write-only channel? In the current design of the language <- arrow is really part of the channel syntax pattern, not a separate operator when it comes to channel types. The syntax is simply not compositional (we cannot interject a <- into the middle of a channel type).

I don't see why this would be worth the effort.

All 12 comments

Isn't this kind of like doing

type MyInt int
var x uMyInt // Is this a uint?

i.e., I wouldn't expect this to work at all.

I'm tentatively marking this as a language change, unless you are suggesting that the current parser is not matching the written spec (in which case this is a bug).

I think it's a language change.

Definitively a language change, possibly backward compatible.

But why would we want to allow such a thing? It makes the direction arrow an operator on types, and only on channel types. And it only can make a bidirectional channel a read-only channel. What if I wanted to create a write-only channel? In the current design of the language <- arrow is really part of the channel syntax pattern, not a separate operator when it comes to channel types. The syntax is simply not compositional (we cannot interject a <- into the middle of a channel type).

I don't see why this would be worth the effort.

This is requesting similar syntax to https://github.com/golang/go/issues/22189.

I'm not saying it is or is not worth the effort, but it definitely a flaw in the design that one can declare read-only channel types only if they are unnamed. I can't think of another property of the language that works like this, for instance that changes the possible types acceptable by a function.

See also #23415.

Take a look at this playground example: https://play.golang.org/p/ih1Uxm4Gfna

package main

import (
    "fmt"
)

type (
    Foo         chan string
    FooReadable <-chan string
    FooWritable chan<- string
)

func Send(f FooWritable, msg string) {
    f <- msg
}

func Recv(f FooReadable) string {
    return <-f
}

func Send2(f chan<- string, msg string) {
    f <- msg
}

func Recv2(f <-chan string) string {
    return <-f
}

func main() {
    Scenario1()
    Scenario2()
    Scenario3()
    Scenario4()
    Scenario5()
}

func Scenario1() {
    // works
    //
    foo := make(chan string)

    // compile error
    //
    // foo := make(Foo)

    go Send(foo, "message")
    fmt.Println(Recv(foo))
}

func Scenario2() {
    // works
    //
    // foo := make(chan string)

    // works as well
    //
    foo := make(Foo)

    go Send2(foo, "message2")
    fmt.Println(Recv2(foo))
}

func Scenario3() {
    foo := make(chan string)

    reader := FooReadable(foo)
    writer := FooWritable(foo)

    go Send(writer, "message3")
    fmt.Println(Recv(reader))

    go Send2(writer, "message4")
    fmt.Println(Recv2(reader))
}

func Scenario4() {
    foo := make(Foo)

    reader := (<-chan string)(foo)
    writer := (chan<- string)(foo)

    go Send(writer, "message5")
    fmt.Println(Recv(reader))

    go Send2(writer, "message6")
    fmt.Println(Recv2(reader))
}

func Scenario5() {
    temp := make(chan string)
    foo := Foo(temp)

    reader := (<-chan string)(foo)
    writer := (chan<- string)(foo)

    go Send(writer, "message7")
    fmt.Println(Recv(reader))

    go Send2(writer, "message8")
    fmt.Println(Recv2(reader))
}

Is this expected behavior? It seems odd to me that type conversion can be done in one direction but not the other:

  1. Foo can become chan string, <-chan string and chan<- string
  2. chan string can become Foo, FooReadable and FooWritable
  3. <-chan string can become FooReadable
  4. chan<- string can become FooWritable
  5. Foo cannot become FooReadable or FooWritable

Is the inability of Foo to be converted in this way intentional?

@mccolljr I don't think this proposal is the right place to discuss the details of Go type system. For the reasons for what you are seeing, see https://golang.org/ref/spec#Assignability. If you want to discuss this, see https://golang.org/wiki/Questions. If you want to propose a change, please use a different issue. Thanks.

@ianlancetaylor i apologize, I guess I was unclear. Please don't talk down to me, though.

I understand assignability and why you can't assign a named type to another named type without explicit conversion. I'm more concerned with why

https://play.golang.org/p/hqpgFfI26uG

...is legal, but explicitly converting a named channel type to a named directional channel type with the same underlying channel type (for ex. chan string) is forbidden, while the conversions, forced or otherwise, between the underlying type (chan string) and a named directional type (FooReadable or FooWritable) is perfectly legal as well.

The assignability rules in the spec don't exaclty make it clear to me that this is correct, and so my above post was meant to lead into that discussion.

It's relevant here because I was originally going to ask whether it would satisfy @davecheney to simply name the types for his receiving and sending directional channels similarly to his named bidirectional channel to offer the visual clarity. I was surprised that this particular conversion between channel types was impossible, given what else is legal in go.

I understand what has been said in this thread regarding why, as proposed, this is complex to add to the language lexically. I'm not sure I understand why this particular brand of conversion isn't allowed, though. I think that this conversion and the proposal are similar enough to warrant discussing this here.

@mccolljr I apologize for making you feel that I was talking down to you. That was in no way my intent.

That said, please keep this issue for discussion of the specific proposal above. Discussions of type assignability rules are simply not related to this proposal, which is not about assignability, or type conversions, at all. Thanks for your consideration.

An possible problem with this proposal might be ambiguity of the parse tree (requiring type information to resolve it). To be investigated.

Was this page helpful?
0 / 5 - 0 ratings