Go中通道的类型别名很奇怪

I want to organize communication between two functions via channels. The callee can only send data to the channel while the caller will wait for it in the select. And I want to show this restriction in the callee signature. Another thing that I want is working with type aliases for channels. For example, instead of having chan string I want to work with MsgChan defined as type MsgChan chan string. And I faced with the problem - the code below won't compile if uncomment line test1(make(Ch)):

package main

import "fmt"

type Ch chan int
type ChIn chan<- int

func test1(in ChIn) {
    fmt.Println(in)
}

func test2(in chan<- int) {
    fmt.Println(in)
}

func main() {
    //test1(make(Ch))
    test1(make(chan int))
    test2(make(Ch))
    test2(make(ChIn))
}

I don't understand why I can't use such approach?

test1() has one parameter of type ChIn. This is a named type. You want to pass a value of type Ch which is a bidirectional channel type, and is also a named type.

So in order for this to compile, a value of Ch should be assignable to type ChIn. This is not allowed by the language specification.

Quoting Assignability (highlighted the one that applies here):

A value x is assignable to a variable of type T ("x is assignable to T") in any of these cases:

  • x's type is identical to T.
  • x's type V and T have identical underlying types and at least one of V or T is not a named type.
  • T is an interface type and x implements T.
  • 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.
  • x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type.
  • x is an untyped constant representable by a value of type T.

You can make it work if you try to pass a value of unnamed type but having the same underlying type, which can be achieved by using a type conversion, e.g.:

test1((chan int)(make(Ch)))

But the above conversion would defeat the purpose of having the named Ch type (as you have to repeat its type literal in order to convert it to an unnamed type just so you can pass it to test1()).

What you should do is don't hide that the type is a channel (don't include chan in the type literal of the type declaration), only create a new type for the element type of the channel, e.g.:

type Msg int

func test(in chan<- Msg) {
    fmt.Println(in)
}

func main() {
    test(make(chan Msg))
    test(make(chan<- Msg))

    ch := make(chan Msg)
    chIn := (chan<- Msg)(ch)
    test(chIn)
}

Try it on the Go Playground.