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.