我喜欢尽可能限制地提供接口,以避免错误使用,以便明确和自我记录。
因此,我希望在用户单向使用时提供定向频道,当然,在内部我有一个双向频道副本。
以下作品的分配:
var internal chan int
var external <-chan int
external = internal
但现在我想向用户提供<-chan chan<- int
类型(在函数的返回中),但以下内容不起作用:
var internal chan chan int
var external <-chan chan<- int
external = internal // this fails
我有两个问题:
<-chan chan<-
类型的变量,但......在任何实际意义上都不能使用这种类型? (因为即使有方向性通道,它们也是在双向编排中使用的AFAIK,并且因为无法进行分配,所以不能以这种方式使用它们)。答案 0 :(得分:4)
规范说明了channel assignability:
[channel]值
x
可分配给T
类型的变量(“x可分配给T”)[当]x
是双向通道值时,{{ 1}}是渠道类型,T
的类型x
和V
具有相同的元素类型,T
或V
中至少有一个不是命名类型。
这恰好反映了您的体验:
T
至chan (chan int)
正常工作<- chan (chan int)
至chan (chan int)
不原因是元素类型(<- chan (chan<- int)
关键字后面的那些)
不平等。
你可以使用它,但不是你想要的方式。无法分配变量 你做的方式,但通过纠正元素类型,你确实可以使用它:
chan
如果您只有var internal chan chan<- int
var external <-chan chan<- int
external = internal
类型,则需要复制值(Examples on play):
chan chan int
答案 1 :(得分:2)
这种情况有点类似于使用用户级泛型的语言中遇到的covariance and contravariance问题。当使用其内部等价的泛型类型时,您也可以在Go中遇到它(它们被称为“复合类型”)。例如:
type A struct{}
// All `B`s are convertible to `A`s
type B A
甚至:
type A interface{}
// B embeds A
type B interface{ A }
var b B
// This works without problem
var a A = A(b)
但请考虑以下情况:
var bs []B
var as []A = ([]A)(bs)
此处,编译失败,错误为cannot use bs (type []B) as type []A in assignment
。虽然任何B
都可以转换为等效A
,但[]B
和[]A
(或chan B
和chan A
不适用,或map[string]B
和map[string]A
,或func(a A)
和func(b B)
或其定义中使用A
和B
的任何其他通用类型。虽然类型可以相互转换,但它们并不相同,而且这些generics
在Go中的工作方式来自规范:
每个类型T都有一个基础类型:如果T是预先声明的类型或类型文字,则相应的基础类型是T本身。否则,T的底层类型是T在其类型声明中引用的类型的基础类型。
type T1 string
type T2 T1
type T3 []T1
type T4 T3
字符串的基础类型T1和T2是字符串。 [] T1,T3和T4的基础类型是[] T1。
请注意,[]T1
的基础类型为[]T1
,而不是[]string
。这意味着[]T2
的基础类型将是[]T2
,而不是[]string
或[]T1
,这使得它们之间的转换不可能。
基本上,你正在尝试做类似的事情:
var internal chan Type1
var external <-chan Type2
external = internal
哪个失败为Type1
和Type2
是两种不同的类型,就类型系统而言。
协方差和逆变是非常难以解决的问题,因为维基百科文章的篇幅或用Java或C#解开有限泛型层的任何时间都会告诉你。这是仿制药如此difficult to implement的原因之一,引起了如此多的争论。
您可以通过在只读和读/写通道之间更深入地使用别名来获得所需的行为,就像您在第一个示例中对internal
/ external
频道所做的那样:
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)
}
同样的事情on the playground。
与你的第一个例子基本相同,只是你必须在“读/写”vs'只读(或写)'别名中更深层次。
答案 2 :(得分:0)
我怀疑这是可能的,因为您必须同时转换外部和内部通道的类型。一次一个工作。
我喜欢将这些发送/接收通道视为一种很好的方法来限制您可以在函数中执行的操作:您有var c chan int
并将其传递给func f(ro <-chan int)
并且现在位于如果发送到ro,你就可以节省开支。不需要显式类型转换。返回c也是如此:return c
中只有func g() <-chan int
。但无论如何,您必须同意通过您的频道传输的类型:这可以是其他双向频道,仅发送或仅接收频道。一旦你发出了通道,你可以转换频道。
chan chan int
是整数通道的通道。您可以将此转换为整数通道<-chan chan int
的仅接收通道或整数通道chan<- chan int
的仅发送通道。无论如何:您发送或接收的内容始终是整数频道chan int
。每个此类都可以在发送/接收之前/之后转换为发送/接收,但您无法将chan chan int
转换为chan chan<-int
,因为这就像将chan chan int
转换为chan chan int32
甚至chan string
。