<p>因为Go在编译期强制校验通道方向,chan<- int和<-chan int底层虽同为chan int,但类型系统禁止反向操作:向只写通道接收或从只读通道发送均直接编译失败,且双向→单向可隐式转换,反之则需显式转换(实际极少需要)。</p>

为什么
chan<- int 和 <-chan int 不能互换?
因为 Go 在编译期就做方向校验,不是运行时检查。把一个只写通道(chan<- int)当成可读的用,比如写 <-ch,编译器立刻报错:invalid operation: <-ch (receive from send-only type chan<- int)。
- 单向类型是“约束型类型”,不是“新类型”——它和双向
chan int底层内存布局完全一致 - 双向通道传给单向参数时,Go 自动隐式转换(如
make(chan int)可直接传给func f(ch chan<- int)) - 但反过来不行:你不能把
chan<- int赋值给chan int变量,会编译失败
这跟接口实现类似:约束越严,越安全;放宽约束必须显式转换(实际极少需要)。
函数参数该用单向 channel 还是双向?
该用单向,而且要按职责选对方向。
立即学习“go语言免费学习笔记(深入)”;
- 发送专用函数,参数必须是
chan<- T:防止调用方误写<-ch导致阻塞或逻辑错乱 - 接收专用函数,参数必须是
<-chan T:防止调用方误写ch <- x,编译直接拒绝 - 不要用
chan T做参数,除非你真需要双向操作(极少见)
例如日志推送函数:func LogTo(ch chan<- string)。如果传入的是双向 ch,Go 自动转成发送型;但如果有人在函数里偷偷加 val := <-ch,根本过不了编译。
make( 为什么几乎没用?
因为它创建的是“只读空通道”,没人能往里写,你也无法从它读出任何东西(除非外部 goroutine 向对应双向通道写)。
-
ch := make(<-chan int)合法,但<-ch永远阻塞 -
ch := make(chan<- int)合法,但你无法接收、也无法 close(close()不接受只写通道) - 真正该用
make创建的,永远是双向通道:make(chan int)或make(chan int, N) - 单向类型只用于声明、参数、返回值——即“使用上下文”,不是“构造上下文”
像 time.Timer.C 或 context.Context.Done() 返回的都是 <-chan struct{},目的就是封死写入口,由内部控制生命周期。
接口定义中怎么用单向 channel 才有效?
在接口方法签名里强制约束实现行为,比文档或注释管用得多。
- 定义接口时用
Send(ch chan<- []byte),所有实现都只能发,不能收 - 如果某个实现偷偷想读数据,编译失败,而不是上线后才发现逻辑错乱
- 返回值也可以是单向:比如
func NewReader() <-chan string,调用方拿到后天然只能读
别指望靠“自觉”或“约定”来防错。Go 的单向 channel 是编译器替你盯住的守门员——它不增加功能,但删掉了最危险的几条错误路径。
单向 channel 最容易被忽略的点,是它不解决“要不要缓冲”“要不要关闭”这类问题,它只解决“能不能这么用”。而恰恰是这个“不能”,让很多 bug 在敲下回车前就被拦住了。



















