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中国”

时间: 2024-10-04 15:11:06

Go 语言编译期断言的相关文章

《Imperfect C++中文版》——1.2 编译期契约:约束

1.2 编译期契约:约束 Imperfect C++中文版本章讲述编译期强制,通常它也被称为"约束(constraints)".遗憾的是,C++并不直接支持约束. Imperfection: C++ 不直接支持约束.C++是一门极其强大和灵活的语言,因此很多支持者(甚至包括一些C++权威)都会认为本节描述的约束实现技术已经足够了.然而,作为C++和约束的双重拥护者,我必须提出我的异议(由于一些很平常的原因).虽然我并不买其他语言鼓吹者的账,然而我同样认为阅读因违反约束而导致的编译错误信

编译时断言

template <bool> struct TAssert;template <> struct TAssert<true> {}; 其使用办法是: TAssert<false> __Assert; 如果模板参数特化为false则编译器会报错,这样实现了一个编译期的断言,而这个错误几乎在所有编译器上面的输出都是一致的,这个可是一个非常有用的特性啊.当然了,因为是在编译期确定,所以传入的模板参数必须是常量才可以.这个东西在boost里面有.需要包含头文件:

c语言-C语言编译NBA数据分析系统怎么连接数据库?

问题描述 C语言编译NBA数据分析系统怎么连接数据库? 连接数据库是不是用C#好一点,哪位大神懂C的,我是小白,请教了 解决方案 c也有连接数据库的库,直接调用

C++:显示接口&amp;amp;运行期多态 和 隐式接口&amp;amp;编译期多态

类(class)和面向对象: 显示接口(explicit interface): 即在源代码中可见, 可以在头文件内看到类的所有接口; 运行期多态(runtime polymorphism):成员函数是virtual, 传入类的引用或指针时, 在运行时, 会自动匹配接口, 可能是基类的接口, 也可能是派生类的; 模板(templates)和泛型编程(generic programming): 隐式接口(implicit interface):typename T, 在函数中, 所必须支持一组操作

C++箴言:理解隐式接口和编译期多态

object-oriented programming(面向对象编程)的世界是围绕着 explicit interfaces(显式接口)和 runtime polymorphism(执行期多态)为中心的.例如,给出下面这个(没有什么意义的)的 class(类). class Widget { public: Widget(); virtual ~Widget(); virtual std::size_t size() const; virtual void normalize(); void s

C语言编译流程

    无论是高级程序设计语言还是专用程序设计语言,都不能被计算机系统直接识别,用这些语言所编写的程序代码称为源程序,源程序需要通过预先设计好的专用程序进行转换,转换为计算机系统可以识别的机器指令,然后才能交由计算机系统执行. 编辑是程序员通过编辑软件录入源代码的过程. 编译是文件录入后,使用编译程序对源文件进行编译,目标文件已经是二进制的机器代码了. 一方面有些程序使用频率高,一般程序员难以自行开发,因此编译系统通常将这些功能预先编译好,以程序库的形式提供给程序员使用. 另一方面,待开发的程序

c语言-关于C语言编译C51单片机程序的一点疑问

问题描述 关于C语言编译C51单片机程序的一点疑问 1.为什么两段不相干的代码会互相影响 在主程序里有一段点亮发光二极管的代码,和一段计数器计数并在数码管上显示的代码.同时工作时,二极管正常点亮,数码管几秒才点亮一回,而且十位显示是闪烁一下就不显示了2.数码管显示时,位选和段选顺序不能改变吗?按照书上的方法是先段选再位选,但是点亮的数码管是本来不应该点亮的,段选和位选顺序换了之后就能正常显示了 解决方案 问题1)你所谓的同时工作是指led亮的同时数码管也在显示,是这样的吗?单片机跑的程序都是单线

CRP多语言编译/测试插件使用之基础环境

引言 最近CRP上线了多言编译/测试插件,作为开发者,我想使用CRP对我的项目进行持续集成,那么如何顺利的完成编译/测试我的项目呢,CRP提供的编译/测试的环境是什么,支持的指令有哪些呢,本文将详细介绍多语言编译/测试插件的基础环境. 什么是CRP的插件 CRP的插件是工作流中的的节点的任务的具体实现方式,比如编译/测试任务是由多语言编译/测试插件具体实现的. 常用指令(可以在crp编译/测试任务输入框内直接输入) apt-get update && apg-get install -y

c语言编译,想自己动手写c语言的编译程序(只完成 分析到生成中间代码部分)

问题描述 c语言编译,想自己动手写c语言的编译程序(只完成 分析到生成中间代码部分) 我们编译原理快学完了,想自己动手写c语言的编译程序(只完成 分析到生成中间代码部分) 我应该如何入手写这个东西,查阅什么资料,反正有什么建议或者能帮助我完成的,给我说说就好 解决方案 windows下安装VC,或者VS 使用教程网上多的是 解决方案二: http://book.douban.com/subject/26339438/http://www.cnblogs.com/Ninputer/archive/