查看原文
其他

别乱用了,用新的。Go SliceHeader 和 StringHeader 将会被废弃!

陈煎鱼 脑子进煎鱼了 2023-07-11

大家好,我是煎鱼。

Go 语言中有个很经典的 (Slice|String)Header,经常出现在大家视野中,为此我写了《Go SliceHeader 和 StringHeader,你知道吗?》给大家介绍,避免被面试官卷到。

以重点来讲,SliceHeader 是 Slice(切片)的运行时表现;StringHeader 是 String(字符串)的运行时表现。

背景

为什么这两个运行时结构体受到了那么多的关注呢?是因为常被用于如下场景:

  • 将 []byte 转换为 string。
  • 将 string 转换为 []byte。
  • 抓取数据指针(data pointer)字段用于 ffi 或其他用途。
  • 将一种类型的 slice 转换为另一种类型的 slice。

常见案例,可见如下代码:

s := "脑子进煎鱼了?重背面试题(doge"
h := (*reflect.StringHeader)(unsafe.Pointer(&s))

又或是自己构造一个:

unsafe.Pointer(&reflect.StringHeader{
  Data: uintptr(unsafe.Pointer(&s.Data[0])),
  Len:  int(s.Size),
 })

似乎看起来没什么问题,所以在业内打开了一种新的姿势。那就是借助 (String|Slice) Header 来实现零拷贝的 string 到 bytes 的转换,得到了广大开发者的使用。毕竟谁都想性能高一点。

如下转换代码:

func main() {
 s := "脑子进煎鱼了"
 v := string2bytes1(s)
 fmt.Println(v)
}

func string2bytes1(s string) []byte {
 stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))

 var b []byte
 pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
 pbytes.Data = stringHeader.Data
 pbytes.Len = stringHeader.Len
 pbytes.Cap = stringHeader.Len

 return b
}

当然,还有更多基于 Header 自己写的转换,甚至写错写到泄露没法被 GC 的,又或是抛出 throw 致命错误查了几周的。

问题

今年 Go 团队进行了讨论,通过分析、搜索发现 reflect.SliceHeaderreflect.StringHeader 在业内经常被滥用,且使用不方便,很容易出错(要命的是很隐性的那种)。

GitHub 搜索发现大量用户案例:

这个坑也在于,SliceHeader 和 StringHeader 的 Data 字段(后称数据指针):

type StringHeader struct {
   Data uintptr
   Len  int
}

类型是 uintptr不是 unsafe.Pointer。设什么都可以,灵活度过于高,非常容易搞出问题。

Go1.20 新特性

在 Go1.20 起,在 unsafe 标准库新增了 3 个函数来替代前面这两个类型的使用。希望能够进一步标准化,并提供额外的类型安全。

如下函数签名:

  • func String(ptr *byte, len IntegerType) string:根据数据指针和字符长度构造一个新的 string。
  • func StringData(str string) *byte:返回指向该 string 的字节数组的数据指针。
  • func SliceData(slice []ArbitraryType) *ArbitraryType:返回该 slice 的数据指针。

新版本的用法将会变成:

func StringToBytes(s string) []byte {
    return unsafe.Slice(unsafe.StringData(s), len(s))
}

func BytesToString(b []byte) string {
    return unsafe.String(&b[0], len(b))
}

以往常用的 reflect.SliceHeaderreflect.StringHeader 将会被标注为被废弃

如下图:

这两个类型老版本中一直被标记为不稳定、不可靠。

以后记得换用法了。

总结

StringHeader 和 SliceHeader 在工作中的底层工具包在前两年非常常见,如果是一个积累比较久的项目可能在内部都偷偷引用了,目的就是为了让自己的性能跑分更优一些。

但是其较大的自由度,带来了过大的类型不安全。因此我在工作中也遇到有人用到莫名其妙抛致命错误,最后只能用排除法去查是哪里的锅。

在新版本 Go1.20 中,Go 官方引入了这几个新函数。能够进一步声明这是 unsafe 的,也能提供一点点的类型安全检查。

当然,新版本后,爱问这几个零拷贝的面试官,也得刷新一下知识库了。

推荐阅读


关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群👇



你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存