今天,我遇到了一个 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. mysql groupby 字段合并问题(group_concat)

    在我们的日常mysql查询中,我们可能会遇到这样的情况: 对表中的所有记录进行分类,并且我需要得到每个分类中某个字段的全部成员. 上面的话,大家看起来可能不太好懂,下面举一个例子来给大家说明. 现在我 ...

  2. Qt_IO系统_二进制读写

    目录 Qt中的读写 QDataStream QDataStream Qt 是如何存储数据的 如何保证读取数据的正确性? --> 魔术数字,文件版本,Qt版本 魔术数字 文件版本 Qt 版本 读取 ...

  3. SpringMVC集成Mybatis

    1.pom.xml中添加引入架包 <dependency> <groupId>mysql</groupId> <artifactId>mysql-con ...

  4. MultipartFile

    转发:原博客 一.MultipartFile是什么? MultipartFile是一个接口并继承了InputStreamSource接口.MockMultipartFile.CommonsMultip ...

  5. C踩坑纪实——(一)

    最近在项目过程中发现了几个c语言中没有注意到的小细节,成功入坑.下面记录的我遇到的问题,以及解决的方法,希望这个过程能给读者带来些许启发. 字符类型变量的溢出 首先来看下面这段代码,你认为会输出什么呢 ...

  6. String常用处理方法

    1.去空格 用于删除字符串的头尾空白符. 语法:public String trim() 返回值:删除头尾空白符的字符串. 删除所有空格 str.replace(" ", &quo ...

  7. ken桑带你读源码 之scrapy scrapy\extensions

    logstats.py 爬虫启动时 打印抓取网页数   item数 memdebug.py 爬虫结束 统计还被引用的内存 也就是说gc 回收不了的内存   memusage.py 监控爬虫 内存占用  ...

  8. jupyter的服务器配置安装

    该教程主要针对的是服务器安装,且在后台保持稳定运行的情况. 1.jupyter下载 有网的时候 1. pip install jupyter 离线安装 在有网络的环境下载安装包 2. pip down ...

  9. Python基础教程 (第2+3 版)打包pdf|内附网盘链接提取码

                <Python基础教程 第3版>包括Python程序设计的方方面面:首先,从Python的安装开始,随后介绍了Python的基础知识和基本概念,包括列表.元组.字符 ...

  10. vjudge CountTables/2018雅礼集训 方阵 dp 斯特林反演

    LINK:CountTables 神题! 首先单独考虑行不同的情况 设\(f_i\)表示此时有i列且 行都不同. 那么显然有 \(f_i=(c^i)^\underline{n}\) 考虑设\(g_i\ ...