Golang中Slice接口推断不可行吗?

Golang中Slice接口推断不可行吗? 我有两个接口,它们有一些相同的方法 Wait()

type A interface {
	Wait() time.Duration
}

type B interface {
	Wait() time.Duration
	Use() time.Duration
}

我有一个结构体 foo 实现了这两个接口

type foo struct {
	wait time.Duration
	use time.Duration
}

func (f* foo) Wait() time.Duration { return f.wait } 
func (f* foo) Use() time.Duration { return f.use } 

我想知道为什么当我使用 []B... 作为参数时,编译器拒绝编译一个带有 ...A 定义参数的函数

func WorkWithTypeA(lst ...A) {}

func main()  {
	listOfElementAsB := []B{&foo{wait:time.Duration(1*time.Second)},&foo{wait:time.Duration(1*time.Second)}}
	WorkWithTypeA(listOfElementAsB... ) // <- 无法编译:不能在参数 WorkWithTypeA 中将 listOfElementAsB(类型 []B)用作类型 []A
}

因为如果我只使用一个参数,这是可以工作的

func main() {
	oneElementAsB:= B(&foo{wait:time.Duration(1*time.Second)})
	WorkWithTypeA(oneElementAsB) // 没有报错
}

在 playground 中的完整示例

有人能给我解释一下发生了什么吗?


更多关于Golang中Slice接口推断不可行吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

更多关于Golang中Slice接口推断不可行吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢,但这并非我所期望的 🙂

我想知道为什么当我使用 []B... 作为参数时,编译器拒绝编译带有 ...A 定义参数的函数

为什么 []B... 没有像 B, B, B 那样将我的切片拆分为元素?因为这才是我预期的结果

func WorkWithTypeA(lst ...A) {}

func main()  {
	listOfElementAsB := []B{&foo{wait:time.Duration(1*time.Second)},&foo{wait:time.Duration(1*time.Second)}}
	WorkWithTypeA(listOfElementAsB[0], listOfElementAsB[1] ) // 没有报错
}

因为 A 和 B 是独立的类型,[]A[]B 也是独立的类型。允许这样做会带来一个非常严重的问题:

func WorksWithAll(all ...interface{}) {
    // ...
}

func main() {
    ints := []int{1, 2, 3, 4}
    WorksWithAll(ints...) // 无法编译
}

这样做的主要问题在于 []int[]interface{} 具有不同的内存布局,因此需要复制切片,最好显式地进行复制。

如果我们允许这样做,还会出现更严重的问题:

func EditFirstElement(i ...interface{}) {
    i[0] = "hello world"
}

func main() {
    allTypes := []interface{}{5, 3+8i, "some more stuff"}
    ints := []int{1, 2, 3, 4, 5}

    EditFirstElement(allTypes...) // 实际上会将 allTypes 的第一个元素改为 "hello world"
    EditFirstElement(ints...) // 糟糕...
}

运行示例:https://play.golang.org/p/-Ezolon-u0R

在Go语言中,切片类型的协变性(covariance)是不支持的,这导致了您遇到的问题。虽然B接口可以赋值给A接口(因为B包含了A的所有方法),但[]B不能直接赋值给[]A,因为它们是不同的类型。

根本原因:

  • Go的类型系统将[]A[]B视为完全不同的类型
  • 即使B可以隐式转换为A,但[]B不能隐式转换为[]A
  • 这是Go类型安全设计的一部分,防止潜在的运行时错误

解决方案: 您需要显式地将[]B转换为[]A

func WorkWithTypeA(lst ...A) {
    // 函数实现
}

func main() {
    listOfElementAsB := []B{&foo{wait: time.Duration(1 * time.Second)}, &foo{wait: time.Duration(1 * time.Second)}}
    
    // 显式转换切片
    listOfElementAsA := make([]A, len(listOfElementAsB))
    for i, elem := range listOfElementAsB {
        listOfElementAsA[i] = elem // 这里发生了隐式接口转换
    }
    
    WorkWithTypeA(listOfElementAsA...)
}

为什么单个元素可以工作:

func main() {
    oneElementAsB := B(&foo{wait: time.Duration(1 * time.Second)})
    WorkWithTypeA(oneElementAsB) // 正常工作
}

这里能编译是因为B接口值可以隐式转换为A接口值,这是Go接口系统的标准行为。

更简洁的写法:

func main() {
    listOfElementAsB := []B{&foo{wait: time.Second}, &foo{wait: 2 * time.Second}}
    
    // 直接在函数调用时转换
    WorkWithTypeA(func() []A {
        result := make([]A, len(listOfElementAsB))
        for i, v := range listOfElementAsB {
            result[i] = v
        }
        return result
    }()...)
}

这种设计确保了类型安全,虽然在某些情况下需要额外的转换代码,但避免了潜在的运行时类型错误。

回到顶部