这个代码竟然报错了?Go 的不可寻址值了解下

简介:   Dave Cheney 在 Twitter 上发布过一个 Go 的小测验,和往常一样,我从中学到了一些有趣的东西。让我们从他的推文开始:  #golang 小测验:该程序打印什么?  package main  import (  "crypto/sha1"  "fmt"  )  func main() {  input := []byte("Hello, playground")  hash := sha1.Sum(input)[:5]  fmt.Println(hash)  }  令我惊讶的是

  Dave Cheney 在 Twitter 上发布过一个 Go 的小测验,和往常一样,我从中学到了一些有趣的东西。让我们从他的推文开始:

  #golang 小测验:该程序打印什么?

  package main

  import (

  "crypto/sha1"

  "fmt"

  )

  func main() {

  input := []byte("Hello, playground")

  hash := sha1.Sum(input)[:5]

  fmt.Println(hash)

  }

  令我惊讶的是,答案是:

  ./test.go:10:28: invalid operation sha1.Sum(input)[:5] (slice of unaddressable value)

  我们收到此错误有三个原因。首先,sha1.Sum()[1] 的返回值不寻常。大多数方法返回切片,而此代码对切片不会报错。但是 sha1.Sum() 返回的值很奇怪,它是一个固定大小的数组(具体来说是 [20]byte ),由于 Go 是返回数值的,这意味着它确实向 main() 返回了 20 字节的数组,而不是指向它的指针。

  这就涉及到了不可寻址值的概念,与可寻址值相反。详细的介绍在 Go 编程语言规范的 地址运算符[2] 中。简单来说,大多数匿名值都不可寻址( 复合字面值[3] 是一个大大的例外)。在上面的代码中,sha1.Sum() 的返回值是匿名的,因为我们立即对其进行了切片操作。如果我们将它存在变量中,并因此使其变为非匿名,则该代码不会报错:

  tmp := sha1.Sum(input)

  hash := tmp[:5]

  最后一个问题是为什么切片操作是错误的。这是因为对数组进行切片操作要求该数组是可寻址的(在 Go 编程语言规范的 Slice 表达式[4] 的末尾介绍)。sha1.Sum() 返回的匿名数组是不可寻址的,因此对其进行切片会被编译器拒绝。

  (将返回值存储到我们的 tmp 变量中使其变成了可寻址。sha1.Sum() 的返回值在复制到 tmp后就消失了。)

  虽然我不能完全理解为什么 Go 的设计师限制了哪些值是可寻址的,但是我可以想到几条原因。例如,如果在这里允许切片操作,那么 Go 会默默地实现堆存储以容纳 sha1.Sum() 的返回值(然后将该值复制到另一个值),该返回值将一直存在直到那个切片被回收。

  (如 x86-64 上的 Go 低级调用惯例[5] 中所述,由于 Go 返回了本科证书的所有值,因此需要将数据进行拷贝。对于 sha1.Sum() 的 20 字节的返回值来说,这并不是什么大事。我很确定人们经常使用更大的结构体作为返回值。)

  PS:Go 语言规范中的许多内容要求或仅对可寻址的值适用。例如,大多数 赋值[6] 操作需要可寻址性。

  假设有一个类型 T,并且在 T 上定义了一些方法,例如 T.Op()。就像 Go 允许在不取消引用指针的情况下进行字段引用一样,你可以在非指针值上调用指针方法:

  var x T

  x.Op()

  这是 (&x).Op() 的简便写法(在 Go 编程语言规范文中靠后的 调用[7] 部分进行了介绍)。但是,由于此简便写法需要获取地址,因此需要可寻址性。因此,以下操作会报错:

  // afunc() 返回一个 T

  afunc().Op()

  // 但是这个可以运行:

  var x T = afunc()

  x.Op()

  之前我已经看到人们在讨论 Go 在方法调用上的怪癖,但是当时我还不完全了解发生了什么,以及由于什么原因使方法调用无法正常工作。

  (请注意,这种简写转换与 *T 具有所有 T 方法是根本不同的,这在我 之前的一篇文章[8] 中提到过)

目录
相关文章
|
4月前
|
Cloud Native Go 开发工具
不改一行代码轻松玩转 Go 应用微服务治理
为了更好的进行 Go 应用微服务治理,提高研发效率和系统稳定性,本文将介绍 MSE 微服务治理方案,无需修改业务代码,实现治理能力。
19861 13
|
1月前
|
安全 Go 开发者
代码之美:Go语言并发编程的优雅实现与案例分析
【10月更文挑战第28天】Go语言自2009年发布以来,凭借简洁的语法、高效的性能和原生的并发支持,赢得了众多开发者的青睐。本文通过两个案例,分别展示了如何使用goroutine和channel实现并发下载网页和构建并发Web服务器,深入探讨了Go语言并发编程的优雅实现。
34 2
|
21天前
|
SQL 监控 算法
为Go应用无侵入地添加任意代码
这篇文章旨在提供技术深度和实践指南,帮助开发者理解并应用这项创新技术来提高Golang应用的监控与服务治理能力。在接下来的部分,我们将通过一些实际案例,进一步展示如何在不同场景中应用这项技术,提供更多实践启示。
|
2月前
|
JSON 搜索推荐 Go
ZincSearch搜索引擎中文文档及在Go语言中代码实现
ZincSearch官网及开发文档均为英文,对非英语用户不够友好。GoFly全栈开发社区将官方文档翻译成中文,并增加实战经验和代码,便于新手使用。本文档涵盖ZincSearch在Go语言中的实现,包括封装工具库、操作接口、统一组件调用及业务代码示例。官方文档https://zincsearch-docs.zinc.dev;中文文档https://doc.goflys.cn/docview?id=41。
|
2月前
|
NoSQL Go Redis
关于kratos proto 生成pb.go的一些报错,问题
关于kratos proto 生成pb.go的一些报错,问题
|
4月前
|
缓存 NoSQL 数据库
go-zero微服务实战系列(五、缓存代码怎么写)
go-zero微服务实战系列(五、缓存代码怎么写)
|
4月前
|
Java 数据库连接 数据库
携手前行:在Java世界中深入挖掘Hibernate与JPA的协同效应
【8月更文挑战第31天】Java持久化API(JPA)是一种Java规范,为数据库数据持久化提供对象关系映射(ORM)方法。JPA定义了实体类与数据库表的映射及数据查询和事务控制方式,确保不同实现间的兼容性。Hibernate是JPA规范的一种实现,提供了二级缓存、延迟加载等丰富特性,提升应用性能和可维护性。通过结合JPA和Hibernate,开发者能编写符合规范且具有高度可移植性的代码,并利用Hibernate的额外功能优化数据持久化操作。
46 0
|
4月前
|
Go C语言
Go语言:新时代的编程英雄,让你的代码驾驭未来!
【8月更文挑战第29天】Go,或称Golang,是由谷歌开发的一种静态强类型的编译语言,旨在融合C语言的高效性和高级语言的易用性。它简洁、优雅,广泛应用于高性能服务器和网络应用开发。本文将通过环境搭建、Hello World示例、变量、常量、控制结构、函数、结构体、接口及错误处理等示例,带你快速入门Go语言,领略其简洁高效的魅力,激发你的编程热情。
40 0
|
4月前
|
API Docker 容器
容器镜像解析问题之使用go-containerregistry在代码中解析容器镜像如何解决
容器镜像解析问题之使用go-containerregistry在代码中解析容器镜像如何解决
42 0