为什么通道的通道方向变化不兼容?

时间:2013-12-11 08:03:49

标签: go channel

我喜欢尽可能限制地提供接口,以避免错误使用,以便明确和自我记录。

因此,我希望在用户单向使用时提供定向频道,当然,在内部我有一个双向频道副本。

以下作品的分配:

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,并且因为无法进行分配,所以不能以这种方式使用它们)。

3 个答案:

答案 0 :(得分:4)

这不起作用的原因

规范说明了channel assignability

  

[channel]值x可分配给T类型的变量(“x可分配给T”)[当] x是双向通道值时,{{ 1}}是渠道类型,T的类型xV具有相同的元素类型,TV中至少有一个不是命名类型。

这恰好反映了您的体验:

  • Tchan (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 Bchan A不适用,或map[string]Bmap[string]A,或func(a A)func(b B)或其定义中使用AB的任何其他通用类型。虽然类型可以相互转换,但它们并不相同,而且这些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

哪个失败为Type1Type2是两种不同的类型,就类型系统而言。

协方差和逆变是非常难以解决的问题,因为维基百科文章的篇幅或用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