今天,我遇到了一个 Go FAQ。首先,作为一个小小的 Go 语言测验,看看您是否在 Go playground 中运行该程序之前就能推断出它应该打印出的内容(我已经将程序放在侧边栏中,以防它在 Go playground 上消失)。该程序的关键代码是:

type fake struct { io.Writer }

func fred (logger io.Writer) {
if logger != nil {
logger.Write([]byte("..."))
}
} func main() {
var lp *fake
fred(nil)
fred(lp)
}

由于 Go 语言中的变量是使用它们的零值显式创建的,在指针的情况下,例如 lp 将会是 nil,您可能期待上述代码会正常运行(即不执行任何操作)。实际上,它会在对 fred() 的第二次调用时崩溃。原因是,在 Go 语言中,有时以 nil 为值的变量,如果直接打印的话,它虽然看起来像 nil,但实际上并不是真的 nil 。简而言之,Go 语言区别对待 nil 接口值和转换为接口的值为 nil 的具体类型。只有前者确实为 nil,因此与字面上的 ni l 相等,就像 fred() 在这里做的一样。

(因此,可以使用 nil f 调用 (f *fake) 上的具体方法。它也许是一个 nil 指针,但是它是类型化的 nil 指针,所以可以拥有有方法。甚至在接口转换后依然可以拥有方法,正如上述的例子。)

对于这里的情况,其解决方法是更改初始化的过程。实际的程序条件性地设置了 fake,类似于下面的代码:

var l *sLogger

if smtplog != nil {
l = &sLogger
l.prefix = logpref
l.writer = bufio.NewWriterSize(smtplog, 4096)
}
convo = smtpd.NewConvo(conn, l)

这会将具体类型为 *sLoggernil 传递给期望参数为 io.Writer 的对象,从而导致接口转换并掩盖了 nil。为了解决这个问题,我们可以添加一个必须显式设置的中间变量 io.Writer

var l2 io.Writer

if smtplog != nil {
l := &sLogger
l.prefix = logpref
l.writer = ....
l2 = l
}
convo = smtpd.NewConvo(conn, l2)

如果我们不初始化这个特殊的日志记录器 sLogger,则 l2 会是一个真正的 io.Writer nil,并会在 smtpd 包中被检测到。

(您可以将类似的初始化操作封装进一个返回类型为 io.Writer 的函数中,并在没有提供日志记录器的情况下显式返回 nil,通过这样的技巧来达到类似的效果。需要强调的一点是,函数必须返回接口类型,如果返回类型为 *sLogger,那么您将再次遇到相同的问题。)

sLogger 的方法中保留对零值的防护代码,这是一个个人喜好问题。然而,我不想这么做,如果将来我在代码中遇到类似的初始化错误,我希望它崩溃,以便对其进行修复。

我从这件事中学到的另一个教训是,如果是出于调试的目的而进行的打印,我不会再使用 %v 作为格式说明符,而会使用 %#v。因为前者将会为接口 nil 和具体类型的 nil 同样打印一个普通且具有误导性的 ,而 `%#v` 将为前者打印出 ,为后者打印 (*main.fake)(nil)

边注栏: 测试程序

package main

import (
"fmt"
"io"
) type fake struct {
io.Writer
} func fred(logger io.Writer) {
if logger != nil {
logger.Write([]byte("a test\n"))
}
} func main() {
// 这里的 t 的值是 nil
var t *fake fred(nil)
fmt.Printf("passed 1\n")
fred(t)
fmt.Printf("passed 2\n")
}

via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoNilNotNil

作者:ChrisSiebenmann 译者:anxk 校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

Go 语言中,有时 nil 并不是一个 nil的更多相关文章

  1. [转]理解Go语言中的nil

    最近在油管上面看了一个视频:Understanding nil,挺有意思,这篇文章就对视频做一个归纳总结,代码示例都是来自于视频. nil是什么 相信写过Golang的程序员对下面一段代码是非常非常熟 ...

  2. C-C++到底支不支持VLA以及两种语言中const的区别

    C-C++到底支不支持VLA以及两种语言中const的区别 到底支不支持VLA VLA就是variable-length array,也就是变长数组. 最近写程序的时候无意间发现,gcc中竟然支持下面 ...

  3. 在 Go 语言中,我为什么使用接口

    强调一下是我个人的见解以及接口在 Go 语言中的意义. 如果您写代码已经有了一段时间,我可能不需要过多解释接口所带来的好处,但是在深入探讨 Go 语言中的接口前,我想花一两分钟先来简单介绍一下接口. ...

  4. [原创]C/C++语言中,如何在main.c或main.cpp中调用另一个.c文件

    C/C++语言中,如何在main.cpp中调用另一个.c文件主要有5种思路: 1.在VS2012 IDE中,将被引用的.c文件后缀名全部修改为.h,然后通过IDE的解决方案资源管理器中鼠标右键单击“头 ...

  5. 【翻译】go语言中的map实战

    业余时间翻译,水平很差,如有瑕疵,纯属无能. 原文链接 http://blog.golang.org/go-maps-in-action go语言中的map实战 1. 简介 哈希表是计算机科学中最重要 ...

  6. [转]Go语言中的make和new

    前言 本文主要给大家介绍了Go语言中函数new与make的使用和区别,关于Go语言中new和make是内建的两个函数,主要用来创建分配类型内存.在我们定义生成变量的时候,可能会觉得有点迷惑,其实他们的 ...

  7. Go语言中的UDP应用

    Go语言中的UDP应用 Go语言中使用UDP是很方便的,net包提供了UDP和TCP的功能,这里使用UDP做了一个UDP广播,然后接收各个设备的返回信息.实现起来很快,总体感觉比使用C#中的UDP更优 ...

  8. Go语言中的map(十一)

    map是一种无序的基于 key-value 的数据结构,Go语言中的map是引用类型,所以跟切片一样需要初始化才能使用. 定义map 定义 map 的语法如下: map[keyType]ValueTy ...

  9. C语言中,头文件和源文件的关系(转)

    简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句, ...

随机推荐

  1. P1092 虫食算(洛谷)

    今天做了一道题,我之前吹牛的时候曾经说:“这个题我觉得深搜剪枝一下就可以了.”. 我觉得我之前说的没错“这个题深搜剪枝亿下,再加点玄学就可以了!” 题目描述 所谓虫食算,就是原先的算式中有一部分被虫子 ...

  2. SpringBoot整合Swagger3生成接口文档

    前后端分离的项目,接口文档的存在十分重要.与手动编写接口文档不同,swagger是一个自动生成接口文档的工具,在需求不断变更的环境下,手动编写文档的效率实在太低.与swagger2相比新版的swagg ...

  3. vue邪道玩法 : 把vue实例存在别的地方,以及可能会遇到的问题

    一般来说,VUE项目中,this指向VUE实例. 但有的时候,某些代码会改变this的指向. 这时,可以用一个临时变量存储VUE实例. test1(){ var _this = this // 把vu ...

  4. corosync+pacemaker高可用集群

    高可用集群,是指以减少服务中断(如因服务器宕机等引起的服务中断)时间为目的的服务器集群技术.简单的说,集群就是一组计算机,它们作为一个整体向用户提供一组网络资源.这些单个的计算机系统就是集群的节点. ...

  5. 【工具】- HttpClient篇

    简介 对于httpclient,相信很多人或多或少接触过,对于httpclient的使用姿势,相信很多人会有疑问?下面这边会通过代码说明 package xxx; import org.apache. ...

  6. 【requests库】七个主要方法

    本文主要介绍requests库访问http的七个主要方法:get.head.post.put.patch.delete. requests.get()方法 get方法用于获取指定url的HTML网页, ...

  7. 题解 CF585F 【Digits of Number Pi】

    考虑用数位 \(DP\) 来统计数字串个数,用 \(SAM\) 来实现子串的匹配. 设状态 \(f(pos,cur,lenth,lim,flag)\),表示数位的位数,在 \(SAM\) 上的节点,匹 ...

  8. 题解 洛谷 P4336 【[SHOI2016]黑暗前的幻想乡】

    生成树计数的问题用矩阵树定理解决. 考虑如何解决去重的问题,也就是如何保证每个公司都修建一条道路. 用容斥来解决,为方便起见,我处理时先将\(n\)减了1. 设\(f(n)\)为用\(n\)个公司,且 ...

  9. Kubernetes实战总结 - DevOps实现

    一.概述 Git:一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理. Jenkins:一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作. M ...

  10. R 常用基本函数

    R 常用的数字.矩阵和数列的处理函数 数值篇 mean() #均值 colMeans() #对列求均值 sum() #求和 max() #最大值 min() #最小值 prod() #连乘 var() ...