Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go2: allow assigning chan chan struct{} to <-chan chan<- struct{} (nested channel direction auto-conversion in general) #21577

Closed
faiface opened this issue Aug 23, 2017 · 8 comments
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@faiface
Copy link

faiface commented Aug 23, 2017

Hello!

This surprised me recently. This is perfectly possible

var ch1 chan int
var ch2 <-chan int
var ch3 chan<- int
ch2 = ch1
ch3 = ch1

Assigning a bi-directional channel to a uni-directional is done without any conversions. And it makes complete sense, there is nothing preventing it.

However, it is not possible when a channel is inside another structure. Examples:

var a struct {
    ch chan int
}

var b struct {
    ch <-chan int
}

b = a // compile error

Note that a and b have anonymous types. The conversion is impossible, although nothing is preventing it.

My specific use-case is a cancellation-confirmation channel. I have a goroutine that I want to cancel and I want that goroutine to report the successful cancellation back. Thus, I pass it a channel of channels. The cancellation signal is a confirmation channel. After a successful cancelation, the goroutine sends a confirmation signal. Like this:

func f(cancel chan chan struct{}) {
    confirm := <-cancel
    close(confirm)
}

func main() {
    cancel := make(chan chan struct{})
    go f(cancel)

    confirm := make(chan struct{})
    cancel <- confirm
    <-confirm
}

Now, I want to restrict the channel directions on the cancelation channel passed to f (f can't cancel itself and so on). So, logically, I change the f's signature like this:

func f(cancel <-chan chan<- struct{}) {

Playground here: https://play.golang.org/p/YTcUnisWHg .

But, this doesn't compile, giving:

tmp/sandbox898600353/main.go:10: cannot use ch (type chan chan struct {}) as type <-chan chan<- struct {} in argument to f

There is no reason why this wouldn't compile. It's just that compiler doesn't auto-"convert" (convert is in quotes, since this is merely a compile time feature, it doesn't really affect any compiled code, or runtime behaviour) channel directions structurally recursively.

I propose it should do it recursively.

Thanks,
Michal Štrba

EDIT: fixed playground link

@gopherbot gopherbot added this to the Proposal milestone Aug 23, 2017
@faiface faiface changed the title proposal: allow assigning chan chan struct{} to <-chan -chan<- struct{} (nested channel direction auto-conversion in general) proposal: allow assigning chan chan struct{} to <-chan chan<- struct{} (nested channel direction auto-conversion in general) Aug 23, 2017
@ianlancetaylor ianlancetaylor changed the title proposal: allow assigning chan chan struct{} to <-chan chan<- struct{} (nested channel direction auto-conversion in general) proposal: Go2: allow assigning chan chan struct{} to <-chan chan<- struct{} (nested channel direction auto-conversion in general) Aug 23, 2017
@ianlancetaylor ianlancetaylor added v2 An incompatible library change LanguageChange Suggested changes to the Go language labels Aug 23, 2017
@ianlancetaylor
Copy link
Member

ianlancetaylor commented Aug 23, 2017

Marking as Go2 since we are not doing language changes right now.

@faiface
Copy link
Author

faiface commented Aug 23, 2017

I want to make this proposal for Go 1. This is a tiny, completely backwards compatible change that is just removing an unwise, unnecessary and artificial limitation.

@ianlancetaylor
Copy link
Member

I'm sorry, I mistyped my comment (I've edited it to correct it). We are not doing language changes right now.

This change looks tiny, but I think it's more complicated than it seems. We have to decide how to handle cases like

type S struct {
    f func(chan <- int) (<- chan int)
    c chan <- <- chan int
}

Maybe it's simple but in effect it's similar to assignment rules for the const qualifier in C, and people are still regularly confused by that (e.g., http://c-faq.com/ansi/constmismatch.html).

@faiface
Copy link
Author

faiface commented Aug 23, 2017

I was thinking about a recursive assignability definition right now, but that comes with complications, because however I tried to define it, it enabled things like this:

var a []int
var b []interface{}
b = a // would pass, bad thing

But, the problem is solvable in an easy way, although one might argue that it's a little ugly. The thing is to redefine type identity of channels from this:

Two channel types are identical if they have identical value types and the same direction.

To this:

Two channel types are identical if they have identical value types and the same direction or if they have identical value types and at least one of them is bidirectional.

This change in the spec would allow what I'm asking for and would prevent things like assigning []int to []interface{}. Also, the change is really simple.

It's possible this change has some gotchas, but I haven't come up with any yet.

@faiface
Copy link
Author

faiface commented Aug 23, 2017

With the above change, this line could be removed from the spec:

x is a bidirectional channel value, T is a channel type, x's type V and T have identical element types, and at least one of V or T is not a named type.

@faiface
Copy link
Author

faiface commented Aug 23, 2017

I just realized that my example/use-case is actually trivially solvable by rewriting this:

package main

func f(cancel <-chan chan<- struct{}) {
	confirm := <-cancel
	close(confirm)
}

func main() {
	cancel := make(chan chan struct{})
	go f(cancel) // compile error

	confirm := make(chan struct{})
	cancel <- confirm
	<-confirm
}

to this

package main

func f(cancel <-chan chan<- struct{}) {
	confirm := <-cancel
	close(confirm)
}

func main() {
	cancel := make(chan chan<- struct{}) // the arrow right here
	go f(cancel) // passes just fine

	confirm := make(chan struct{})
	cancel <- confirm
	<-confirm
}

However, this still might be useful in more complicated scenarios.

@bcmills
Copy link
Contributor

bcmills commented Aug 25, 2017

This proposal assumes that the in-memory representation for chan and chan<- is identical. That's true today, but is it really something we want to bake into the language?

@faiface
Copy link
Author

faiface commented Sep 5, 2017

@bcmills That's a fair point and I actually no longer feel like this proposal is a good idea. First, I've been able to solve my problem as I described above. Second, all situation imaginable where this would be useful seem like bad code with duplications.

@faiface faiface closed this as completed Sep 5, 2017
@golang golang locked and limited conversation to collaborators Sep 5, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

4 participants