I like to program providing interfaces as restrictively as possible, both to avoid bad usage as to be explicit and self-documented.
So, I like to provide directional channels when they are supposed to be used unidirectionally by the user, but of course, internally I have a bidirectional channel copy.
Assigment for the following works:
var internal chan int
var external <-chan int
external = internal
But now I want to provide to the user a <-chan chan<- int
type (in the return of a function), but the following won't work:
var internal chan chan int
var external <-chan chan<- int
external = internal // this fails
I have two questions:
<-chan chan<-
type, but... can't use such a type in any practical sense? (Because even though there're directional channels, they're AFAIK aways used in orchestration with bidirectional ones, and since assignment is not possible, they can't be used this way)The specification says this about channel assignability:
A [channel] value
x
is assignable to a variable of typeT
("x is assignable to T") [when]x
is a bidirectional channel value,T
is a channel type,x
's typeV
andT
have identical element types, and at least one ofV
orT
is not a named type.
This reflects exactly what you're experiencing:
chan (chan int)
to <- chan (chan int)
workschan (chan int)
to <- chan (chan<- int)
does notThe reason for this is that the element types (the ones after the chan
keyword)
are not equal.
You can use it but not the way you want to. It is not possible to assign the variables the way you do but by correcting the element types you can indeed use it:
var internal chan chan<- int
var external <-chan chan<- int
external = internal
If you only have your chan chan int
type you need to copy your values (Examples on play):
var internal chan chan int
var internalCopy chan chan<- int
go func() { for e := range internal { internalCopy <- e } }()
var external <-chan chan<- int
external = internalCopy
This is a case somewhat akin to the problems of covariance and contravariance encountered in languages with user-level generics. You can also encounter it in Go when using its internal equivalents of generic types (they're called 'composite types'). For instance:
type A struct{}
// All `B`s are convertible to `A`s
type B A
Or even:
type A interface{}
// B embeds A
type B interface{ A }
var b B
// This works without problem
var a A = A(b)
But consider the following case:
var bs []B
var as []A = ([]A)(bs)
Here, the compilation fails with error cannot use bs (type []B) as type []A in assignment
. Although any B
can be converted to the equivalent A
, this does not hold true for []B
and []A
(or chan B
and chan A
, or map[string]B
and map[string]A
, or func(a A)
and func(b B)
or any other generic type using A
s and B
s in their definition). Although types are convertible to one another, they are not the same, and the way these generics
work in Go is, from the spec:
Each type T has an underlying type: If T is a predeclared type or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its type declaration.
type T1 string
type T2 T1
type T3 []T1
type T4 T3
The underlying type of string, T1, and T2 is string. The underlying type of []T1, T3, and T4 is []T1.
Note here that the underlying type of []T1
is []T1
, not []string
. This means that the underlying type of []T2
will be []T2
, not []string
or []T1
, which makes conversion between them impossible.
Basically, you are trying to do something like:
var internal chan Type1
var external <-chan Type2
external = internal
Which fails as Type1
and Type2
are two different types, as far as the type system is concerned.
Covariance and contravariance are very difficult problems, as the length of the wikipedia article or any time spent untangling layers of bounded generics in Java or C# will tell you. It is one of the reason generics are so difficult to implement and raise so many debate.
You can get the behaviour you want by going one level deeper on your aliases between read-only and read/write channels, exactly as you did with your internal
/external
channel on your first example:
package main
import "fmt"
// This has the correct signature you wanted
func ExportedFunction(c <-chan (chan<- int)) {
// Sends 1 to the channel it receives
(<-c)<- 1
}
func main() {
// Note that this is a READ/WRITE channel of WRITE-ONLY channels
// so that the types are correct
internal := make(chan (chan<- int))
var external <-chan (chan<- int)
// This works because the type of elements in the channel is the same
external = internal
// This channel is internal, so it is READ/WRITE
internal2 := make(chan int)
// This is typically called externally
go ExportedFunction(external)
fmt.Println("Sending channel...")
// The type of internal makes the receiving end of internal/external
// see a WRITE-ONLY channel
internal <- internal2
fmt.Println("We received:")
fmt.Println(<-internal2)
}
The same thing on the playground.
Basically the same thing as your first example, except that you have to go one level deeper in the 'read/write' vs 'read(or write) only' aliases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With