一尘不染

为什么无法转换切片类型?

go

我想知道为什么你不能做:

type Foo struct { A int }
type Bar Foo

foos := []Foo{Foo{1}, Foo{2}}
bars := []Bar(foos)
//cannot convert foos (type []Foo) to type []Bar

我发现这将需要运行时在片上执行循环以转换每个元素,这将是非惯用的Go。这很有道理。

然而,这可能不会被刚刚走样编译器解决BarFoo,所以在内部它们是相同的,他们使用相同类型的头底下?我猜答案虽然不是我好奇为什么。


阅读 259

收藏
2020-07-02

共1个答案

一尘不染

这个:

[]Bar(foos)

是类型转换。根据规范,转换具有特定的规则:

在以下任何一种情况下,x可以将非恒定值转换为类型T

  • x分配T
  • x的类型,并且T具有相同的基础类型。
  • x的类型和T是未命名的指针类型,它们的指针基类型具有相同的基础类型。
  • x的类型T均为整数或浮点类型。
  • x的类型和T都是复杂类型。
  • x是整数或字节或符文的片段,并且T是字符串类型。
  • x是一个字符串,T是字节或符文的一部分。

这里没有适用。为什么?

因为的基础类型[]Foo与的基础类型不同[]Bar类型[]Foo值不能分配给类型变量[]Bar,请参见此处的可分配性规则

的基础类型Foo与的基础类型Bar相同,但不适用于元素类型为Foo和的切片Bar

因此,以下工作原理:

type Foo struct{ A int }

type Foos []Foo
type Bars Foos

func main() {
    foos := []Foo{Foo{1}, Foo{2}}
    bars := Bars(foos)

    fmt.Println(bars)
}

输出(在Go Playground上尝试):

[{1} {2}]

注意,由于实际内存中的表示FooBar是相同的(由于底层类型BarFoo使用包),在此情况下unsafe可以通过“视图”的值[]Foo作为值[]Bar

type Foo struct{ A int }
type Bar Foo

func main() {
    foos := []Foo{Foo{1}, Foo{2}}

    bars := *(*[]Bar)(unsafe.Pointer(&foos))

    fmt.Println(bars)
    fmt.Printf("%T", bars)
}

这:*(*[]Bar)(unsafe.Pointer(&foos))表示取的地址foos,将其转换为unsafe.Pointer根据规范可以将所有指针转换为unsafe.Pointer),然后将Pointer其转换为*[]Bar(再次根据规范Pointer可以将其转换为任何其他指针类型),然后将该指针取消引用(*运算符),因此结果是[]Bar在输出中可以看到的type值。

输出(在Go Playground上尝试):

[{1} {2}]
[]main.Bar

笔记:

引用的软件包文档unsafe

不安全的软件包包含绕过Go程序的类型安全的操作。

导入不安全的软件包可能是不可移植的,并且不受Go 1兼容性准则的保护。

这是什么意思?这意味着您不应该usafe每次都使用包来简化生活。您仅应在特殊情况下使用它,否则将使您的程序确实非常缓慢和复杂。

在您的程序中,情况并非如此,因为我提出了一个仅需一点重构(Foos并且Bars是切片)的工作示例。

unsafe绕过Go的类型安全。这是什么意思?如果您要更改的类型foos(例如,大幅度地更改foos := "trap!"),您的程序仍会编译并运行,但是很可能会发生运行时恐慌。使用它们usafe会丢失编译器的类型检查。

如果您使用我的其他提案(FoosBars),则会在编译时检测到此类更改/错别字。

2020-07-02