Go 语言编译期断言

这篇文章是关于一个鲜为人知的让 Go 在编译期断言的方法。你可能不会使用它,但是了解一下也很有趣。

作为一个热身,来看一个在 Go 中熟知的编译期断言:接口满意度检查。

在这段代码(playground)中,var _ = 行确保类型 W 是一个 stringWriter,其由 io.WriteString检查。

 
 
  1. package main
  2. import "io"
  3. type W struct{}
  4. func (w W) Write(b []byte) (int, error) { return len(b), nil }
  5. func (w W) WriteString(s string) (int, error) { return len(s), nil }
  6. type stringWriter interface {
  7. WriteString(string) (int, error)
  8. }
  9. var _ stringWriter = W{}
  10. func main() {
  11. var w W
  12. io.WriteString(w, "very long string")
  13. }

如果你注释掉了 W 的 WriteString 方法,代码将无法编译:

 
 
  1. main.go:14: cannot use W literal (type W) as type stringWriter in assignment:
  2. W does not implement stringWriter (missing WriteString method)

这是很有用的。对于大多数同时满足 io.Writer 和 stringWriter 的类型,如果你删除 WriteString 方法,一切都会像以前一样继续工作,但性能较差。

你可以使用编译期断言保护你的代码,而不是试图使用`testing.T.AllocsPerRun'为性能回归编写一个脆弱的测试。

这是一个实际的 io 包中的技术例子


好的,让我们低调一点!

接口满意检查是很棒的。但是如果你想检查一个简单的布尔表达式,如 1 + 1 == 2 ?

考虑这个代码(playground):

 
 
  1. package main
  2. import "crypto/md5"
  3. type Hash [16]byte
  4. func init() {
  5. if len(Hash{}) < md5.Size {
  6. panic("Hash is too small")
  7. }
  8. }
  9. func main() {
  10. // ...
  11. }

Hash 可能是某种抽象的哈希结果。init 函数确保它将与 crypto/md5 一起工作。如果你改变 Hash 为(比如说)[8]byte,它会在进程启动时发生崩溃。但是,这是一个运行时检查。如果我们想要早点发现怎么办?

如下。(没有 playground 链接,因为这在 playground 上不起作用。)

 
 
  1. package main
  2. import "C"
  3. import "crypto/md5"
  4. type Hash [16]byte
  5. func hashIsTooSmall()
  6. func init() {
  7. if len(Hash{}) < md5.Size {
  8. hashIsTooSmall()
  9. }
  10. }
  11. func main() {
  12. // ...
  13. }

现在如果你改变 Hash 为 [8]byte,它将在编译过程中失败。(实际上,它在链接过程中失败。足够接近我们的目标了。)

 
 
  1. $ go build .
  2. # demo
  3. main.hashIsTooSmall: call to external function
  4. main.init.1: relocation target main.hashIsTooSmall not defined
  5. main.init.1: undefined: "main.hashIsTooSmall"

这里发生了什么?

hashIsTooSmall 是一个没有函数体的声明。编译器假定别人将提供一个实现,也许是一个汇编程序。

当编译器可以证明 len(Hash {})< md5.Size 时,它消除了 if 语句中的代码。结果,没有人使用函数hashIsTooSmall,所以链接器会消除它。没有其他损害。一旦断言失败,if 语句中的代码将被保留。不会消除hashIsTooSmall。链接器然后注意到没有人提供了函数的实现然后链接失败,并出现错误,这是我们的目标。

最后一个奇怪的点:为什么是 import "C"? go 工具知道在正常的 Go 代码中,所有函数都必须有主体,并指示编译器强制执行。通过切换到 cgo,我们删除该检查。(如果你在上面的代码中运行 go build -x,而没有添加 import "C" 这行,你会看到编译器是用 -complete 标志调用的。)另一种方法是添加 import "C"向包中添加一个名为 foo.s 的空文件

我仅见过一次这种技术的使用,是在编译器测试套件中。还有其他可以发挥想象力的使用,但我还没见到过。

原文发布时间为:2017-03-28

本文来自云栖社区合作伙伴“Linux中国”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值