【问题标题】:Conversion of a slice of string into a slice of custom type将字符串切片转换为自定义类型切片
【发布时间】:2025-12-11 16:00:01
【问题描述】:

我对 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,或者我必须创建一个新结构并将数据复制到其中? (我想避免这种情况,因为我需要调用的函数会修改切片内容)。

【问题讨论】:

  • type Card string 您正在引入一个实际的新类型,而不是类型别名。 Card 是一种新类型,它可以拥有自己的方法和行为集。

标签: go type-conversion slice


【解决方案1】:

Card 的底层类型可能与string 的底层类型相同(其本身为:string),但[]Card 的底层类型与@ 的底层类型不同987654329@(因此同样适用于Hand)。

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

但回到根源:如果您需要Cards 的切片,为什么首先要创建strings 的切片?您创建了 type Card,因为它不是 string(或至少不只是 string)。如果是这样并且您需要[]Card,那么首先创建[]Card,您的所有问题都会消失:

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

请注意,您仍然可以使用 untyped 常量 string literals 来初始化 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 Language Specification: Conversions

why []string can not be converted to []interface{} in golang

Cannot convert []string to []interface {}

What about memory layout means that []T cannot be converted to []interface in Go?

【讨论】:

  • 正如我所提到的,这是一个小例子,在我的实际设置中,[]string 来自我无法控制的外部来源。
  • @FDO ​​是的,我知道,但如果这是一个宠物示例,那么在更复杂的示例中,更有理由首先创建正确的类型。看看这个例子,你没有所需的输入切片,只有一些数据,你可以从中生成所需的输入切片。
  • 我不确定你的意思。例如,strings.Split 的输出是[]string。我创建了 CardHand 类型,因为我想要这些类型的“方法”,而 strings.Split 的输出用作这些类型的输入,而仅仅复制输入似乎是浪费时间/cpu/代码在新的数据结构中。为什么[]string[]Card 有不同的内存布局? ([]int32[]byte 的例子很明显,但就我而言,这对我来说并不那么明显)
  • @FDO ​​如果你有一个[]string,你需要从它手动构建一个[]Card。没有“更简单”的方法。您可以创建一个帮助器toCards() 函数,这样您就可以在需要的任何地方使用它。
  • 好的。谢谢你。我仍在尝试找出这种设计选择背后的基本原理,所以如果您有任何我可以阅读的文章,我将不胜感激。
【解决方案2】:

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

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

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

软件包文档中的强制性警告:

unsafe.Pointer 允许程序破坏类型系统并读写任意内存。使用时应格外小心。

在 2011 年有一个关于转化和基础类型的 discussion on the mailing list,在 2016 年有一个 proposal to allow conversion between recursively equivalent types 被拒绝,“直到有更令人信服的理由”。

【讨论】:

  • 这是一个非常有趣的补充评论,我希望我可以多次 +1 以增加其知名度。
【解决方案3】:

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

完全正确。您必须通过循环和复制每个元素来转换它,在途中将类型从string 转换为Card

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

因为转换始终是显式的,并且设计人员认为,当转换隐式涉及副本时,它也应该是显式的。

【讨论】:

  • 仍然,我不明白为什么需要副本(有没有办法避免它?)。在我的理解中仍然存在缺失的联系。
  • @FDO,它是一个副本,因为一切都是一个值。没有办法在不复制的情况下创建变量并赋值,即使它只是一个指针。