英文:
Why are slices of directional channels failing to compile?
问题
以下是翻译好的内容:
上面的代码片段出现以下错误:
./prog.go:28:6: 无法将 ch (类型为 []chan int) 作为 read 函数的参数类型 []<-chan int 使用
./prog.go:29:7: 无法将 ch (类型为 []chan int) 作为 write 函数的参数类型 []chan<- int 使用
如何在 read
和 write
函数中使用通道方向使其正常工作?
英文:
https://play.golang.org/p/-cHBgiNl0tK
package main
import (
"fmt"
"time"
)
func read(ch ...<-chan int) {
for i := 0; i < len(ch); i++ {
i := i
go func() { fmt.Println(<-ch[i]) }()
}
}
func write(ch ...chan<- int) {
for i := 0; i < len(ch); i++ {
i := i
go func() { ch[i] <- i }()
}
}
func main() {
var maxLen = 10
var ch []chan int
for i := 0; i < maxLen; i++ {
ch = append(ch, make(chan int))
}
read(ch...)
write(ch...)
time.Sleep(10 * time.Second)
}
The above snippet is failing with below error
./prog.go:28:6: cannot use ch (type []chan int) as type []<-chan int in argument to read
./prog.go:29:7: cannot use ch (type []chan int) as type []chan<- int in argument to write
How can I make it work with channel directions in read
and write
functions?
答案1
得分: 4
你正在尝试将[]chan int
转换为[]<-chan int
。看起来像是一个简单的转换,但实际上这是一个深度转换,因为你正在改变容器的元素类型。在Go语言中,这种转换是不允许的,尽管有一个已存在的(已关闭的)提案,允许进行这种类型的通道切片转换:https://github.com/golang/go/issues/41695
以下是我过去在类似情况下使用的解决方案:
func readOnlyChannels(slice []chan int) []<-chan int {
return *(*[]<-chan int)(unsafe.Pointer(&slice))
}
func writeOnlyChannels(slice []chan int) []chan<- int {
return *(*[]chan<- int)(unsafe.Pointer(&slice))
}
然后使用方式:
read(readOnlyChannels(ch)...)
write(writeOnlyChannels(ch)...)
由于通道的只读或只写属性仅在编译时的类型信息中存在,并不在类型的实际数据表示中**[需要引用]**,你可以使用unsafe
来强制进行转换。
注意:我不知道是否有任何明确或隐含的保证unsafe
转换是有效的,所以你应该创建测试来验证它在目标平台上是否有效。然而,在实践中,它似乎是有效的。它依赖于不同方向的通道值具有相同(或至少兼容)的数据表示。从实际角度来看,通道方向性的设计没有理由要求或甚至从在运行时保持通道方向性中获益。
如果你想完全避免使用unsafe
,那么你将不得不进行"长"转换,像这样:
func readOnlyChannels(slice []chan int) []<-chan int {
out := make([]<-chan int, len(slice))
for i := range slice {
out[i] = slice[i]
}
return out
}
func writeOnlyChannels(slice []chan int) []chan<- int {
out := make([]chan<- int, len(slice))
for i := range slice {
out[i] = slice[i]
}
return out
}
这里唯一的真正缺点是,在进行转换时,它将通过将所有内容复制到一个新的切片中导致更多的分配。在你的应用程序中使用最不会引起问题的方法。
英文:
You're trying to pass a []chan int
as a []<-chan int
. Seems like a simple conversion, but it's actually a deep conversion as you're changing the element type of the container. These conversions aren't allowed in Go, though there is an existing (closed) proposal to make this kind of channel slice conversion allowed: https://github.com/golang/go/issues/41695
Here's a solution I've used in a similar situation in the past:
func readOnlyChannels(slice []chan int) []<-chan int {
return *(*[]<-chan int)(unsafe.Pointer(&slice))
}
func writeOnlyChannels(slice []chan int) []chan<- int {
return *(*[]chan<- int)(unsafe.Pointer(&slice))
}
And then usage:
read(readOnlyChannels(ch)...)
write(writeOnlyChannels(ch)...)
Since the read-only or write-only quality of a channel is only carried in the type information at compile time, and not in the actual data representation of the type [citation needed], you can use unsafe
to just force a conversion.
Note: I'm not aware of any explicit or implicit guarantee that the unsafe
conversion is valid, so you should create tests to verify that this works on your target platforms. However, in practice, it seems to work. It relies on differently-directioned channel values having identical (or at least compatible) data representations. Practically speaking, there's no reason why the design of channel directionality would require or even benefit from maintaining the channel direction at run-time.
If you want to avoid unsafe
completely, then you'll have to do the "long" conversion like this:
func readOnlyChannels(slice []chan int) []<-chan int {
out := make([]<-chan int, len(slice))
for i := range slice {
out[i] = slice[i]
}
return out
}
func writeOnlyChannels(slice []chan int) []chan<- int {
out := make([]chan<- int, len(slice))
for i := range slice {
out[i] = slice[i]
}
return out
}
The only real downside here is that it's going to cause more allocations by copying everything into a new slice when you do the conversion. Use whichever approach poses the least issue in your application.
答案2
得分: 3
尽管类型为chan int
的表达式可以分配给类型为chan<- int
(只发送通道)或<-chan int
(只接收通道)的变量,但对于这些类型的切片却不适用:
var (
ch chan int
_ <-chan int = ch // 可行
_ chan<- int = ch // 可行
_ []<-chan int = []chan int(nil) // 编译错误
_ []chan<- int = []chan int(nil) // 编译错误
)
./prog.go:7:2: cannot use ([]chan int)(nil) (type []chan int) as type []<-chan int in assignment
./prog.go:8:2: cannot use ([]chan int)(nil) (type []chan int) as type []chan<- int in assignment
正如Hymns for Disco在他/她的回答中指出的,不幸的是没有简单的解决方法。我不建议使用unsafe
包,因为unsafe
的风险有点抵消了使用通道方向的好处。我只会在函数write
和read
的参数中省略通道方向。具体情况可能有所不同。
英文:
Although an expression of type chan int
is assignable to a variable of type either chan<- int
(send-only channel) or <-chan int
(receive-only channel), the same doesn't apply to slices of such types:
var (
ch chan int
_ <-chan int = ch // ok
_ chan<- int = ch // ok
_ []<-chan int = []chan int(nil) // compilation error
_ []chan<- int = []chan int(nil) // compilation error
)
./prog.go:7:2: cannot use ([]chan int)(nil) (type []chan int) as type []<-chan int in assignment
./prog.go:8:2: cannot use ([]chan int)(nil) (type []chan int) as type []chan<- int in assignment
As pointed out by Hymns for Disco in his/her answer, there is unfortunately no easy way out. I wouldn't recommend resorting to the unsafe
package myself, because the risks associated with unsafe
kind of offset the benefits of using channel directions. I would simply drop the channel directions in the parameters to functions write
and read
. YMMV.
答案3
得分: 0
使用支持方向通道的通道的替代方法。
package main
import (
"fmt"
"time"
)
func read(inputs chan (<-chan int)) {
for input := range inputs {
go func(input <-chan int) { fmt.Println(<-input) }(input)
}
}
func write(outputs chan chan<- int) {
i := 0
for output := range outputs {
go func(output chan<- int, i int) { output <- i }(output, i)
i += 1
}
}
func main() {
var maxLen = 10
readers := make(chan (<-chan int), 10)
writers := make(chan chan<- int, 10)
for i := 0; i < maxLen; i++ {
ch := make(chan int)
readers <- ch
writers <- ch
}
close(readers)
close(writers)
read(readers)
write(writers)
time.Sleep(10 * time.Second)
}
英文:
Alternative approach with channel of channels with support for directional channels.
package main
import (
"fmt"
"time"
)
func read(inputs chan (<-chan int)) {
for input := range inputs {
go func(input <-chan int) { fmt.Println(<-input) }(input)
}
}
func write(outputs chan chan<- int) {
i := 0
for output := range outputs {
go func(output chan<- int, i int) { output <- i }(output, i)
i += 1
}
}
func main() {
var maxLen = 10
readers := make(chan (<-chan int), 10)
writers := make(chan chan<- int, 10)
for i := 0; i < maxLen; i++ {
ch := make(chan int)
readers <- ch
writers <- ch
}
close(readers)
close(writers)
read(readers)
write(writers)
time.Sleep(10 * time.Second)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论