将字符串的切片转换为自定义类型的切片

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

Conversion of a slice of string into a slice of custom type

问题

我对Go语言还不太熟悉,所以这可能很明显。编译器不允许以下代码:
(http://play.golang.org/p/3sTLguUG3l)

package main

import "fmt"

type Card string
type Hand []Card

func NewHand(cards []Card) Hand {
    hand := Hand(cards)
    return hand
}

func main() {
    value := []string{"a", "b", "c"}
    firstHand := NewHand(value)
    fmt.Println(firstHand)
}

错误信息是:
/tmp/sandbox089372356/main.go:15: cannot use value (type []string) as type []Card in argument to NewHand

从规范来看,[]string 与 []Card 不是相同的底层类型,所以类型转换无法进行。

确实是这样吗?还是我漏掉了什么?

如果是这样的话,为什么会这样呢?假设在一个非宠物示例程序中,我有一个字符串切片作为输入,有没有办法将其“转换”为 Card 切片,或者我必须创建一个新的结构并将数据复制到其中?(我希望避免这样做,因为我需要调用的函数将修改切片内容)。

英文:

I'm quite new to Go, so this might be obvious. The compiler does not allow the following code:
(http://play.golang.org/p/3sTLguUG3l)

package main

import "fmt"

type Card string
type Hand []Card

func NewHand(cards []Card) Hand {
    hand := Hand(cards)
	return hand
}

func main() {
    value := []string{"a", "b", "c"}
	firstHand := NewHand(value)
    fmt.Println(firstHand)
}

The error is:
/tmp/sandbox089372356/main.go:15: cannot use value (type []string) as type []Card in argument to NewHand

From the specs, it looks like []string is not the same underlying type as []Card, so the type conversion cannot occur.

Is it, indeed, the case, or did I miss something?

If it is the case, why is it so? Assuming, in a non-pet-example program, I have as input a slice of string, is there any way to "cast" it into a slice of Card, or do I have to create a new structure and copy the data into it? (Which I'd like to avoid since the functions I'll need to call will modify the slice content).

答案1

得分: 26

没有技术上的原因禁止在元素具有相同底层类型的切片之间进行转换(例如[]string[]Card)。这是一项规范决定,旨在避免在偶然具有相同结构的不相关类型之间发生意外转换。

安全的解决方案是复制切片。然而,可以使用unsafe包直接进行转换(无需复制):

value := []string{"a", "b", "c"}
// 将 &value(类型为 *[]string)通过 unsafe.Pointer 转换为 *[]Card,然后解引用
cards := *(*[]Card)(unsafe.Pointer(&value))
firstHand := NewHand(cards)

https://play.golang.org/p/tto57DERjYa

请注意,根据该包的文档,有一个强制性的警告:

unsafe.Pointer 允许程序绕过类型系统读取和写入任意内存。应该非常小心地使用它。

在2011年的邮件列表中有关于转换和底层类型的讨论,以及在2016年有一个关于允许在递归等价类型之间进行转换的提案,但被拒绝了,原因是“直到有更有说服力的理由”。

英文:

There is no technical reason why conversion between slices whose elements have identical underlying types (such as []string and []Card) is forbidden. It was a specification decision to help avoid accidental conversions between unrelated types that by chance have the same structure.

The safe solution is to copy the slice. However, it is possible to convert directly (without copying) using the unsafe package:

value := []string{"a", "b", "c"}
// convert &value (type *[]string) to *[]Card via unsafe.Pointer, then deref
cards := *(*[]Card)(unsafe.Pointer(&value))
firstHand := NewHand(cards)

https://play.golang.org/p/tto57DERjYa

Obligatory warning from the package documentation:

> unsafe.Pointer allows a program to defeat the type system and read and write arbitrary memory. It should be used with extreme care.

There was a discussion on the mailing list about conversions and underlying types in 2011, and a proposal to allow conversion between recursively equivalent types in 2016 which was declined "until there is a more compelling reason".

答案2

得分: 15

Card的底层类型可能与string的底层类型相同(即string本身),但[]Card的底层类型与[]string的底层类型不同(因此对Hand也适用)。

你不能将T1的切片转换为T2的切片,这不是它们具有不同的底层类型的问题,如果T1T2不相同,你就不能转换。为什么呢?因为不同元素类型的切片可能具有不同的内存布局(在内存中的大小不同)。例如,类型为[]byte的元素每个占用1个字节。类型为[]int32的元素每个占用4个字节。显然,即使所有值都在0..255的范围内,你也不能将一个转换为另一个。

但回到根本问题:如果你需要一个Card的切片,为什么首先要创建一个string的切片呢?你创建了Card类型,因为它不是一个string(或者至少不仅仅是一个string)。如果是这样,并且你需要[]Card,那么首先创建[]Card,所有问题都会解决:

value := []Card{"a", "b", "c"}
firstHand := NewHand(value)
fmt.Println(firstHand)

请注意,你仍然可以使用未命名的常量string字面值初始化Card的切片,因为它可以用于初始化底层类型为string的任何类型。如果你想使用具有类型的string常量或类型为string的非常量表达式,你需要进行显式转换,如下面的示例所示:

s := "ddd"
value := []Card{"a", "b", "c", Card(s)}

如果你有一个[]string,你需要手动从中构建一个[]Card。没有更简单的方法。你可以创建一个辅助函数toCards(),这样你就可以在需要的任何地方使用它。

func toCards(s []string) []Card {
	c := make([]Card, len(s))
	for i, v := range s {
		c[i] = Card(v)
	}
	return c
}

一些背景和原因的链接:

Go语言规范:转换

https://stackoverflow.com/questions/21325155/why-string-can-not-be-converted-to-interface-in-golang

https://stackoverflow.com/questions/12990338/cannot-convert-string-to-interface

https://stackoverflow.com/questions/29026241/what-about-memory-layout-means-that-t-cannot-be-converted-to-interface-in-go

英文:

The underlying type of Card might be the same as the underlying type of string (which is itself: string), but the underlying type of []Card is not the same as the underlying type of []string (and therefore the same applies to Hand).

You cannot convert a slice of T1 to a slice of T2, it's not a matter of what underlying types they have, if T1 is not identical to T2, you just can't. Why? Because slices of different element types may have different memory layout (different size in memory). For example the elements of type []byte occupy 1 byte each. The elements of []int32 occupy 4 bytes each. Obviously you can't just convert one to the other even if all values are in the range 0..255.

But back to the roots: if you need a slice of Cards, why do you create a slice of strings in the first place? You created the type Card because it is not a string (or at least not just a string). If so and you require []Card, then create []Card in the first place and all your problems go away:

value := []Card{"a", "b", "c"}
firstHand := NewHand(value)
fmt.Println(firstHand)

Note that you are still able to initialize the slice of Card with untyped constant string literals because it can be used to initialize any type whose underlying type is string. If you want to involve typed string constants or non-constant expressions of type string, you need explicit conversion, like in the example below:

s := "ddd"
value := []Card{"a", "b", "c", Card(s)}

If you have a []string, you need to manually build a []Card from it. There is no "easier" way. You can create a helper toCards() function so you can use it everywhere you need it.

func toCards(s []string) []Card {
	c := make([]Card, len(s))
	for i, v := range s {
		c[i] = Card(v)
	}
	return c
}

Some links for background and reasoning:

Go Language Specification: Conversions

https://stackoverflow.com/questions/21325155/why-string-can-not-be-converted-to-interface-in-golang

https://stackoverflow.com/questions/12990338/cannot-convert-string-to-interface

https://stackoverflow.com/questions/29026241/what-about-memory-layout-means-that-t-cannot-be-converted-to-interface-in-go

答案3

得分: 1

从规范上看,[]string与[]Card不是相同的底层类型,所以无法进行类型转换。

完全正确。你必须通过循环和逐个复制每个元素来进行转换,将类型从string转换为Card

如果是这种情况,为什么会这样呢?假设在一个非宠物示例程序中,我输入了一个字符串切片,有没有办法将其“转换”为Card切片,或者我必须创建一个新的结构并将数据复制到其中?(我希望避免这样做,因为我需要调用的函数将修改切片内容)。

因为转换总是显式的,设计者们认为,当转换隐式地涉及到复制时,也应该将其明确化。

英文:

> From the specs, it looks like []string is not the same underlying type as []Card, so the type conversion cannot occur.

Exactly right. You have to convert it by looping and copying over each element, converting the type from string to Card on the way.

> If it is the case, why is it so? Assuming, in a non-pet-example program, I have as input a slice of string, is there any way to "cast" it into a slice of Card, or do I have to create a new structure and copy the data into it? (Which I'd like to avoid since the functions I'll need to call will modify the slice content).

Because conversions are always explicit and the designers felt that when a conversion implicitly involves a copy it should be made explicit as well.

huangapple
  • 本文由 发表于 2015年3月13日 19:42:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/29031353.html
匿名

发表评论

匿名网友

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

确定