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) // 没有报错
}
有人能给我解释一下发生了什么吗?
更多关于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...) // 糟糕...
}
在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
}()...)
}
这种设计确保了类型安全,虽然在某些情况下需要额外的转换代码,但避免了潜在的运行时类型错误。

