为什么你无法转换Slice类型?

huangapple go评论94阅读模式
英文:

Why are you unable convert Slice types?

问题

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

type Foo struct { A int }
type Bar Foo

foos := []Foo{Foo{1}, Foo{2}}
bars := []Bar(foos)
//无法将foos(类型为[]Foo)转换为类型[]Bar

我发现这需要运行时对切片进行循环,以转换每个元素,这在Go语言中是非惯用的。这是有道理的。

然而,编译器是否可以通过将Bar别名为Foo来解决这个问题,这样它们在内部就是相同的,并且它们在底层使用相同的类型头?我猜答案是否定的,但我很好奇为什么。

英文:

I was wondering why you can't do:

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

and I found out that this would require the runtime to perform a loop over the slice to convert each of the elements, which would be non-idiomatic Go. This makes sense.

However, could this not be solved by the compiler just aliasing Bar as Foo, so internally they're the same and they use the same type header underneath? I'm guessing the answer is no though I'm curious as to why.

答案1

得分: 3

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

  • 在以下情况下,非常量值 x 可以转换为类型 T
    • x 可以赋值给 T
    • x 的类型和 T 具有相同的基础类型。
    • x 的类型和 T 都是未命名的指针类型,并且它们的指针基础类型具有相同的基础类型。
    • x 的类型和 T 都是整数或浮点数类型。
    • x 的类型和 T 都是复数类型。
    • x 是整数或字节或符文切片,而 T 是字符串类型。
    • x 是字符串,而 T 是字节或符文切片。

在这里,上述规则都不适用。为什么呢?

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

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)
}

输出结果为:

[{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)
}

*(*[]Bar)(unsafe.Pointer(&foos)) 的意思是取 foos 的地址,将其转换为 unsafe.Pointer(根据规范,所有指针都可以转换为 unsafe.Pointer),然后将此 Pointer 转换为 *[]Bar(再次根据规范,Pointer 可以转换为任何其他指针类型),然后对此指针进行解引用操作(* 运算符),因此结果是一个类型为 []Bar 的值,如输出结果所示。

输出结果为:

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

注意:

引用 unsafe 包的文档:

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

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

这意味着你不应该每次都使用 unsafe 包来简化你的生活。只有在特殊情况下,当不使用它会使你的程序变得非常缓慢和复杂时,才应该使用它。

在你的程序中,我提出了一个工作示例,只需进行一点点重构(使用切片 FoosBars)。

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

而如果你使用我的另一个建议(FoosBars),这样的更改/拼写错误会在编译时被检测到。

英文:

This:

[]Bar(foos)

is a type conversion. Conversions have specific rules according to the spec:

> A non-constant value x can be converted to type T in any of these cases:
>
> - x is assignable to T.
> - x's type and T have identical underlying types.
> - x's type and T are unnamed pointer types and their pointer base types have identical underlying types.
> - x's type and T are both integer or floating point types.
> - x's type and T are both complex types.
> - x is an integer or a slice of bytes or runes and T is a string type.
> - x is a string and T is a slice of bytes or runes.

None applies here. Why?

Because the underlying type of []Foo is not the same as the underlying type of []Bar. And a value of type []Foo is not assignable to a variable of type []Bar, see Assignability rules here.

The underlying type of Foo is the same as the underlying type of Bar, but the same does not apply to slices where the element type is Foo and Bar.

So the following works:

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)
}

Output (try it on the Go Playground):

[{1} {2}]

Note that since the actual memory representation of Foo and Bar is the same (because the underlying type of Bar is Foo), in this case using the package unsafe you can "view" a value of []Foo as a value of []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)) means that take the address of foos, convert it to unsafe.Pointer (according to spec all pointers can be converted to unsafe.Pointer), then this Pointer is converted to *[]Bar (again according to the spec Pointer can be converted to any other pointer type), and then this pointer is dereferenced (* operator), so the result is a value of type []Bar as can be seen in the output.

Output (try it on the Go Playground):

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

Notes:

Quoting the package doc of unsafe:

> Package unsafe contains operations that step around the type safety of Go programs.
>
> Packages that import unsafe may be non-portable and are not protected by the Go 1 compatibility guidelines.

What does this mean? It means that you shouldn't revert to using package usafe every time it makes your life easier. You should only use it in exceptional cases, when not using it would make your program really slow and complicated.

In your program this is not the case as I proposed a working example with just a little refactoring (Foos and Bars being slices).

unsafe steps around the type safety of Go. What does this mean? If you would change the type of foos (e.g. drastically like foos := "trap!"), your program would still compile and run, but most likely runtime panic would occur. Using usafe you lose type checks of the compiler.

While if you use my other proposal (Foos and Bars), such changes/typos are detected at compile time.

答案2

得分: 1

如在“为什么我可以输入别名函数并在不进行转换的情况下使用它们?”中提到的

在Go中,没有类型别名的概念。type关键字引入了新的**命名类型**。它们不是别名。

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

这是规范中提到的

类型声明将标识符(类型名称)绑定到一个具有与现有类型相同底层类型的新类型,并且为现有类型定义的操作也适用于新类型。
新类型与现有类型不同

英文:

As mentioned in "Why can I type alias functions and use them without casting?"

> In Go, there is no such thing as a type alias.
The type keyword introduces new named types. They are not aliases

> If you compare two named types, the names must match in order for them to be interchangeable

This is what the spec mentions:

> A type declaration binds an identifier, the type name, to a new type that has the same underlying type as an existing type, and operations defined for the existing type are also defined for the new type.
The new type is different from the existing type.

huangapple
  • 本文由 发表于 2015年8月8日 17:06:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/31891493.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定