【问题标题】:Why are you unable convert Slice types?为什么不能转换 Slice 类型?
【发布时间】:2015-08-08 09:06:28
【问题描述】:

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

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。这是有道理的。

但是,编译器是否不能通过将Bar 别名为Foo 来解决这个问题,所以在内部它们是相同的并且它们在下面使用相同的类型标头?我猜答案是否定的,虽然我很好奇为什么。

【问题讨论】:

  • 您实际上是在询问如何绕过类型系统来优化循环。我认为unsafe 包可以让你这样做

标签: go type-conversion slice type-alias


【解决方案1】:

这个:

[]Bar(foos)

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

在以下任何一种情况下,非常量值 x 都可以转换为类型 T

  • xassignableT
  • x 的类型和T 具有相同的底层类型。
  • x 的类型和 T 是未命名的指针类型,它们的指针基类型具有相同的基础类型。
  • x 的类型和T 都是整数或浮点类型。
  • x 的类型和T 都是复杂类型。
  • x 是整数或字节切片或符文,T 是字符串类型。
  • x 是一个字符串,T 是一个字节或符文片段。

此处不适用。为什么?

因为[]Foo 的基础类型与[]Bar 的基础类型不同。 而且[]Foo 类型的值不能分配给[]Bar 类型的变量,见Assignability rules here

Foo 的底层类型与 Bar 的底层类型相同,但同样不适用于元素类型为 FooBar 的切片。

所以以下工作:

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的实际内存表示是相同的(因为Bar的底层类型是Foo),在这种情况下使用包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)
}

this:*(*[]Bar)(unsafe.Pointer(&foos))表示取foos的地址,转换成unsafe.Pointeraccording to spec所有指针都可以转换成unsafe.Pointer),那么这个Pointer就转换成*[]Bar (再次根据规范 Pointer 可以转换为任何其他指针类型),然后取消引用该指针(* 运算符),因此结果是类型为 []Bar 的值,如输出所示.

输出(在Go Playground 上试试):

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

注意事项:

引用unsafe的包文档:

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

导入 unsafe 的包可能是不可移植的,并且不受 Go 1 兼容性指南的保护。

这是什么意思?这意味着您不应该每次都使用 usafe 包让您的生活更轻松。你应该只在特殊情况下使用它,如果不使用它会使你的程序变得非常缓慢和复杂。

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

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

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

【讨论】:

  • 感谢@icza,所以底层表示是相同的,只是阻止转换的规范 - 不安全的转换是我所追求的!
  • @jpillora 请查看已编辑的答案。不要仅仅因为它更容易就使用unsafe。如果可能,请使用适当的设计以避免使用unsafe
  • 足够公平并同意 - 我目前正在循环转换每个元素,如果性能受到影响,我只会使用 unsafe ,而它很可能不会。也许我猜它可以完全避免?用例是:我正在导入一个包foo,我想在我的包中公开它的类型foo.Foo,所以我重新输入它以防止我的用户需要导入我的包和 foo.
【解决方案2】:

如“Why can I type alias functions and use them without casting?”中所述

在 Go 中,没有类型别名这种东西
type 关键字引入了新的 named types。他们不是别名

如果您比较两个命名类型,名称必须匹配才能使它们可互换

这就是spec mentions

类型声明将标识符(类型名称)绑定到与现有类型具有相同基础类型的新类型,并且为现有类型定义的操作也为新类型定义。
新类型与现有类型不同

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-20
    • 2014-01-26
    • 2018-11-04
    • 1970-01-01
    • 2023-03-12
    • 2021-02-14
    • 2018-02-15
    相关资源
    最近更新 更多