0.遇到一个问题

代码

func GetMap (i interface{})(map[string]interface{}){
if i == nil { //false ???
i = make(map[string]interface)
fmt.Println("xxxxx")
}
} var testMap map[string]interface{}
getMap := GetMap(testMap)
getMap["add"] = "add" //panic

问题:比较困惑的是对于一个传进来的testMap是nil,但是在GetMap 里面if却是false。实践看来函数内部形参确实已经不是nil。那么interface{}判断nil的具体是什么过程?

找答案: 看了这个视频understanding nil 整理了一下

总结答案

  • 空interface实际数据结构是包含type和value两个字段。
  • 判断interface==nil的时候需要type和value都是null,才等于nil。
  • testMap赋值给i之后,接口包含了赋值对象的类型细信息。的type字段不再是null了,因此代码if的结果就是false。

持续学习

通过遇到的问题引申学习主要解决两个问题

  1. nil到底是什么?
    第一部分标题1-3整理了一些nil在go中的使用,对于不同类型对nil比较的实际操作并举了例子。
  2. interface的存储了什么?
    第二部分标题4对interface实际内存中结构进行了探索。包括带方法的interface和空interface

1.nil是什么

个人理解: 学习之前类比为c的null空指针。学习之后知道有点以偏概全

官方定义:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type
// Type must be a pointer, channel, func, interface, map, or slice type
  • nil 并不是关键字,只是预定义的标识符。
  • nil代表数据类型的0值,并不仅仅是空指针。
  • nil 可以和 pointer, channel, func, interface, map, or slice 进行比较
不同类型对应的0值如下:
类型 零值
numbers 0
string ""
bool false
pointer nil
slices nil
maps nil
channels nil
functions nil
interfaces nil

结构体的0值
对于结构体来说,每个字段都是nil值,整个结构体才是nil

type Person struct {
Age int
Name string
Friends []Person
}
var p Person // Person{0, "", nil}

2.nil类型

nil 没有类型 不是关键字 可被修改

var nil = errors.New("***")

不同类型对应nil实际判断标准

类型 实际存储 nil判断
pointers 类c 不指向任何内存, 内存安全 垃圾回收
slices [ptr(*elem)\ len()|cap()]
maps,channels,functions ptr 没有初始化
interface (type,data) (nil,nil)
  • 特别的 -- interface 的 nil
    interface 包含type 和 data
    nil interface指的是 type是nil,且 value 是nil
var s fmt.Stringer // Stringer(nil,nil)
fmt.Println(s == nil) //true
//(nil, nil) == nil var p *Person //nil of type *Person
var s fmt.Stringer = p //Stringer(*Person nil)
fmt.Println(s == nil) // false
//(*Person, nil) != nil
  • nil interface 不等于 nil??

错误例子:这里和最开始的问题类似,函数返回error的时候,定义了一个空的error并返回,在函数碗面判断error!=nil的时候并不是true。所以在实际开发的时候,对于没有error的情况,要直接返回nil。

type error interface {
Error() string
} func do() error { // 实际 error(*doError, nil)
var err *doError
return err //类型是 *doError 的nil
}
func main(){
err := do() //error (*doError , nil)
fmt.Println(err == nil) //false
}

正确方式:
不定义error类型,直接返回nil

func do() *doError {    //nil of type *doError
return nil
}
func main(){
err := do() //nil of type *doError
fmt.Println(err == nil) //true
}

对于多层函数调用
里层函数定义了返回值虽然是nil,但是包含了type类型。所以避免这种写法

func do() *doError{     //nil of type *doError
return nil
}
func wrapDo error { //error(*doError , nil)
return do() //nil of type *doError
}
func main(){
err := wrapDo() //error(*doError,nil)
fmt.Println(err == nil)//false
}

综上:不要声明具体error类型,以为的nil interface实际上已经不是nil了。以上和文章问题的初衷是一致的。因为某种类型的nil赋值给nil interface之后 interface!=nil了。 赋值后的interface虽然没有值,但是已经有类型信息了

nil 不仅仅是 null

3.不同type nil的操作

pointers 取值panic

var p *int
p == nil //true
*p //panic nil receiver

slices 可遍历 可append 不可取值panic

var s []slice
len(s) // 0
cap(s) // 0
for range s // zero times
s[i] // panic:index out of range append // ok 自动扩容 1024以内2倍扩容 以上1.25倍

maps 可以遍历 可取值 可赋值

var m map[t]u
len(m) //0
for range m { // zero times
v,ok := m[i] // zero(u), false
m[i] = x
}

channels 读写阻塞 close会panic

//nil channel
var c chan t
<-c //blocks forever
c<-x // blocks forever
close(c) // panic: close of nil channel //closed channel
v, ok <- c //zero(t),false
c <- x //panic: send on closed channel
close(c) //panic: close of nil channel

interfaces

  type Summer interface{
func Sum() int
} //pointer
var t *tree
var s Summer = t
fmt.Println(t == nil, s.Sum() ) //true, 0 //slice
type ints []int
func (i ints)Sum() int{
s:=0
for _, v := range i {
s += v
}
return s
}
var i ints
var s Summer = i
fmt.Println( i == nil, s.Sum()) //true , 0 // nil value can satisfy nil interface

4.interface

gopher 讲的 interface使用Tomas Senart - Embrace the Interface
Google Understanding Go Interfaces

  • writing generic algorithm
  • hiding implementation detail
  • providing interception points

用于声明方法集合,可嵌套,不包含方法实现。不定义字段。
优势:隐藏一些具体的实现,泛型编程,不用声明实现哪些func运行时确定

4.1 interface数据结构

有方法的接口

iface

iface 是 runtime 中对 interface 进行表示的根类型 (src/runtime/runtime2.go)

type iface struct{
tab *itab //类型信息
data unsafe.Pointer //实际对象指针
}
type itab struct {
inter *interfacetype //接口类型
_type *type //实际对象类型
fun [1]uintptr //实际对象方法地址
...
}
  • iface 的 itab字段存储接口定义的方法相关信息,method 的具体实现存放在 itab.fun变量里。描述interface类型和其指向的数据类型的数据结构可以看下面gdb调试过程结构的打印.

  • data存储interface持有的具体的值信息,不可被修改。当赋值的a被修改并不会影响interface里面的data

    itab
  • _type 这个类型是 runtime 对任意 Go 语言类型的内部表示。_type 类型描述了一个“类型”的每一个方面: 类型名字,特性(e.g. 大小,对齐方式...),某种程度上类型的行为(e.g. 比较,哈希...) 也包含在内了。。(src/runtime/type.go)

  • interfacetype 的指针,这只是一个包装了 _type 和额外的与 interface 相关的信息的字段。描述了interface本身的类型。(src/runtime/type.go)

  • func 数组持有组成该interface虚表的的函数的指针。

空接口

  • 空接口可以被任何类型赋值,默认值是nil。
  • 没有方法
  • 存储结构也和有方法的interface不同。如下
eface

(src/runtime/runtime2.go)

type eface struct {
_type *_type //对象类型信息
data unsafe.Pointer //对象指针
} type _type struct {
size uintptr // type size
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldalign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
alg *typeAlg // algorithm table
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
  • eface没有方法声明,存储*_type包含类型信息。可以看到一个空接口也存了两个字段,这里根本解释了最开始提到的问题,对于判断interface{}==nil的时候,需要保证接口两个字段都是null才是true。下面4.2debug的例子中调试29行,34行和35行对比ei的时候可以看到,虽然一个nil struct赋值给了interface{}后,空接口的_type,data字段都已经不是null了。
  • interface被赋值之后也支持比较。(如果赋值对象支持比较)
func main(){
var t1,t2 interface{}
fmt.Println(t1==nil) //true
fmt.Println(t1==t2) //true t1,t2=100,100
fmt.Println(t1==t2) // true t1,t2=map[string]int{},map[string]int{}
fmt.Println(t1==t2)} //panic runtime error:comparing uncomparable type map[string]int
}

断言 .(asert)

interface{}可作为函数参数,实现泛型编程。
asert 用于判断变量类型 并且 可以判断变量是否实现了某个接口

type data int
func(d data)String()string{
return fmt.Sprintf("data:%d",d)
}
func main(){
var d data=15
var x interface{}=d
if n,ok:=x.(fmt.Stringer);ok{ //转换为更具体的接口类型
fmt.Println(n)
}
if d2,ok:=x.(data);ok{ //转换回原始类型
fmt.Println(d2)
}
e:=x.(error) //错误:main.data is not error
fmt.Println(e)
}
  • 使用ok-idiom模式不用担心转换失败时候panic
  • 利用switch可以在多种类型条件下进行匹配 ps type switch不支持fallthrought
func main(){
var x interface{}=func(x int)string{return fmt.Sprintf("d:%d",x)}
switchv:=x.(type){ //局部变量v是类型转换后的结果
case nil :
fmt.Println("nil")
case*int:
fmt.Println(*v)
case func(int)string:
fmt.Println( v(100) )
case fmt.Stringer:
fmt.Println(v)
default:
fmt.Println("unknown")
}
}
output:100

4.2 debug一下

code

  1 package main
2 import(
3 "fmt"
4 )
5
6 type A struct {
7
8 }
9 type Aer interface {
10 AerGet()
11 AerSet()
12 }
13 func (a A)AerGet(){
14 fmt.Println("AerGet")
15 }
16
17 func (a *A)AerSet(){
18 fmt.Println("AerSet")
19 }
20 func main(){
21 var a A
22 var aer Aer
23 aer = &a
24 aer.AerGet()
25 aer.AerSet()
26
27 var ei interface{}
28 if ei == nil {
29 fmt.Println("ei is nil")
30 }else {
31 fmt.Println("ei not nil")
32 }
33 var aa A
34 ei = aa
35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
38 fmt.Println("ei not nil")
39 }
40
41 }

debug

mac版本10.14.2 gdb版本8.2.1。mac系统更当前新版本之后gdb并不能使用了,尝试创建证书授权给gdb,但是并没有成功。教程gdb wiki
因此使用了lldb。主要打印了eface和iface内存,利用用nil struct对interface{}赋值之后interface{}的内存变化。

* thread #1, stop reason = breakpoint 1.1
frame #0: 0x000000000109376b test`main.main at interfacei.go:23
20 func main(){
21 var a A
22 var aer Aer
-> 23 aer = &a
24 aer.AerGet()
25 aer.AerSet()
26
Target 0: (test) stopped.
(lldb) p aer //interface iface struct
(main.Aer) aer = {
tab = 0x0000000000000000
data = 0x0000000000000000
}
(lldb) n
21 var a A
22 var aer Aer
23 aer = &a
-> 24 aer.AerGet()
25 aer.AerSet()
26
27 var ei interface{}
Target 0: (test) stopped.
(lldb) p aer
(main.Aer) aer = {
tab = 0x000000000112c580
data = 0x000000000115b860
}
(lldb) p &aer
(*main.Aer) = 0x000000000112c580
(lldb) p *aer.tab // itab struct
(runtime.itab) *tab = {
inter = 0x00000000010acc60
_type = 0x00000000010aba80
link = 0x0000000000000000
hash = 474031097
bad = false
inhash = true
unused = ([0] = 0, [1] = 0)
fun = ([0] = 0x0000000001093a10)
}
(lldb) p *aer.tab._type
(runtime._type) *_type = {
size = 0x0000000000000008
ptrdata = 0x0000000000000008
hash = 474031097
tflag = 1
align = 8
fieldalign = 8
kind = 54
alg = 0x000000000113bd50
gcdata = 0x00000000010d4ae4
str = 6450
ptrToThis = 0
}
(lldb) p *aer.tab.inter //inter struct
(runtime.interfacetype) *inter = {
typ = {
size = 0x0000000000000010
ptrdata = 0x0000000000000010
hash = 2400961726
tflag = 7
align = 8
fieldalign = 8
kind = 20
alg = 0x000000000113bd80
gcdata = 0x00000000010d4ae6
str = 10139
ptrToThis = 45184
}
pkgpath = {
bytes = 0x0000000001093e78
}
mhdr = (len 2, cap 2) {
[0] = (name = 5032, ityp = 61664)
[1] = (name = 5041, ityp = 61664)
}
} (lldb) n
ei is nil
27 var ei interface{}
28 if ei == nil {
29 fmt.Println("ei is nil")
30 }else {
31 fmt.Println("ei not nil")
32 }
33 var aa A //aa == nil
-> 34 ei = aa
35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
Target 0: (test) stopped.
(lldb) p ei //interface{} == ni
(interface {}) ei = { //eface struct
_type = 0x0000000000000000
data = 0x0000000000000000
}
(lldb) n
32 }
33 var aa A
34 ei = aa
-> 35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
38 fmt.Println("ei not nil")
(lldb) p ei
(interface {}) ei = { //interface{} != nil
_type = 0x00000000010acbe0
data = 0x000000000115b860
}
(lldb) p *ei._type // _type struct
(runtime._type) *_type = {
size = 0x0000000000000000
ptrdata = 0x0000000000000000
hash = 875453117
tflag = 7
align = 1
fieldalign = 1
kind = 153
alg = 0x000000000113bd10
gcdata = 0x00000000010d4ae4
str = 6450
ptrToThis = 98304
}

汇编

汇编代码看不大懂,放在这里督促学习。

go build -gcflags '-l' -o interfacei interfacei.go
go tool objdump -s "main\.main" interfacei TEXT main.main(SB) /Users/didi/go/src/test/interface/interfacei.go
interfacei.go:20 0x10936b0 65488b0c25a0080000 MOVQ GS:0x8a0, CX
interfacei.go:20 0x10936b9 483b6110 CMPQ 0x10(CX), SP
interfacei.go:20 0x10936bd 0f8635010000 JBE 0x10937f8
interfacei.go:20 0x10936c3 4883ec70 SUBQ $0x70, SP
interfacei.go:20 0x10936c7 48896c2468 MOVQ BP, 0x68(SP)
interfacei.go:20 0x10936cc 488d6c2468 LEAQ 0x68(SP), BP
interfacei.go:20 0x10936d1 488d05e8920100 LEAQ 0x192e8(IP), AX interfacei.go:21 0x10936d8 48890424 MOVQ AX, 0(SP)
interfacei.go:21 0x10936dc e8afb7f7ff CALL runtime.newobject(SB)
interfacei.go:21 0x10936e1 488b442408 MOVQ 0x8(SP), AX
interfacei.go:21 0x10936e6 4889442430 MOVQ AX, 0x30(SP)
//24 aer.AerGet()
interfacei.go:24 0x10936eb 48890424 MOVQ AX, 0(SP)
interfacei.go:24 0x10936ef e87c010000 CALL main.(*A).AerGet(SB)
interfacei.go:24 0x10936f4 488b442430 MOVQ 0x30(SP), AX
//25 aer.AerSet()
interfacei.go:25 0x10936f9 48890424 MOVQ AX, 0(SP)
interfacei.go:25 0x10936fd e82effffff CALL main.(*A).AerSet(SB) interfacei.go:29 0x1093702 48c744244800000000 MOVQ $0x0, 0x48(SP)
interfacei.go:29 0x109370b 48c744245000000000 MOVQ $0x0, 0x50(SP)
interfacei.go:29 0x1093714 488d0505030100 LEAQ 0x10305(IP), AX
interfacei.go:29 0x109371b 4889442448 MOVQ AX, 0x48(SP)
interfacei.go:29 0x1093720 488d0dd9240400 LEAQ 0x424d9(IP), CX
interfacei.go:29 0x1093727 48894c2450 MOVQ CX, 0x50(SP)
interfacei.go:29 0x109372c 488d4c2448 LEAQ 0x48(SP), CX
interfacei.go:29 0x1093731 48890c24 MOVQ CX, 0(SP)
interfacei.go:29 0x1093735 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:29 0x109373e 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:29 0x1093747 e8b48dffff CALL fmt.Println(SB)
interfacei.go:29 0x109374c 488d056d920100 LEAQ 0x1926d(IP), AX
// 35 if ei == nil {
interfacei.go:35 0x1093753 4885c0 TESTQ AX, AX
interfacei.go:35 0x1093756 7454 JE 0x10937ac
//38 fmt.Println("ei not nil")
interfacei.go:38 0x1093758 48c744245800000000 MOVQ $0x0, 0x58(SP)
interfacei.go:38 0x1093761 48c744246000000000 MOVQ $0x0, 0x60(SP)
interfacei.go:38 0x109376a 488d05af020100 LEAQ 0x102af(IP), AX
interfacei.go:38 0x1093771 4889442458 MOVQ AX, 0x58(SP)
interfacei.go:38 0x1093776 488d05a3240400 LEAQ 0x424a3(IP), AX
interfacei.go:38 0x109377d 4889442460 MOVQ AX, 0x60(SP)
interfacei.go:38 0x1093782 488d442458 LEAQ 0x58(SP), AX
interfacei.go:38 0x1093787 48890424 MOVQ AX, 0(SP)
interfacei.go:38 0x109378b 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:38 0x1093794 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:38 0x109379d e85e8dffff CALL fmt.Println(SB) interfacei.go:41 0x10937a2 488b6c2468 MOVQ 0x68(SP), BP
interfacei.go:41 0x10937a7 4883c470 ADDQ $0x70, SP
interfacei.go:41 0x10937ab c3 RET interfacei.go:36 0x10937ac 48c744243800000000 MOVQ $0x0, 0x38(SP)
interfacei.go:36 0x10937b5 48c744244000000000 MOVQ $0x0, 0x40(SP)
interfacei.go:36 0x10937be 488d055b020100 LEAQ 0x1025b(IP), AX
interfacei.go:36 0x10937c5 4889442438 MOVQ AX, 0x38(SP)
interfacei.go:36 0x10937ca 488d053f240400 LEAQ 0x4243f(IP), AX
interfacei.go:36 0x10937d1 4889442440 MOVQ AX, 0x40(SP)
interfacei.go:36 0x10937d6 488d442438 LEAQ 0x38(SP), AX
interfacei.go:36 0x10937db 48890424 MOVQ AX, 0(SP)
interfacei.go:36 0x10937df 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:36 0x10937e8 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:36 0x10937f1 e80a8dffff CALL fmt.Println(SB) interfacei.go:35 0x10937f6 ebaa JMP 0x10937a2 interfacei.go:20 0x10937f8 e883aafbff CALL runtime.morestack_noctxt(SB)
interfacei.go:20 0x10937fd e9aefeffff JMP main.main(SB)

参考

Go Interface 源码剖析
Tomas Senart - Embrace the Interface
Google Understanding Go Interfaces
understanding nil

goalng nil interface浅析的更多相关文章

  1. Go语言第一深坑:interface 与 nil 的比较

    interface简介 Go 语言以简单易上手而著称,它的语法非常简单,熟悉 C++,Java 的开发者只需要很短的时间就可以掌握 Go 语言的基本用法. interface 是 Go 语言里所提供的 ...

  2. 【荐】详解 golang 中的 interface 和 nil

    golang 的 nil 在概念上和其它语言的 null.None.nil.NULL一样,都指代零值或空值.nil 是预先说明的标识符,也即通常意义上的关键字.在 golang 中,nil 只能赋值给 ...

  3. SQLite浅析

    对于iOS工程师有一道常考的面试题,即iOS数据存储的方式 标答如下: Plist(NSArray\NSDictionary) Preference (偏好设置\NSUserDefaults) NSC ...

  4. 理解Go Interface

    理解Go Interface 1 概述 Go语言中的接口很特别,而且提供了难以置信的一系列灵活性和抽象性.接口是一个自定义类型,它是一组方法的集合,要有方法为接口类型就被认为是该接口.从定义上来看,接 ...

  5. Go“一个包含nil指针的接口不是nil接口”踩坑

    最近在项目中踩了一个深坑--"Golang中一个包含nil指针的接口不是nil接口",总结下分享出来,如果你不是很理解这句话,那推荐认真看下下面的示例代码,避免以后写代码时踩坑. ...

  6. 重学Golang系列(一): 深入理解 interface和reflect

    前言 interface(即接口),是Go语言中一个重要的概念和知识点,而功能强大的reflect正是基于interface.本文即是对Go语言中的interface和reflect基础概念和用法的一 ...

  7. 如何理解golang中的nil

    nil的奇怪行为 刚接触golang时,发现nil在不同的上下文,行为表现是不同的,并且和其他语言中的表现,也不大相同 实例1:输入true, true, false,不符合传递性 func main ...

  8. Golang | 既是接口又是类型,interface是什么神仙用法?

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是golang专题的第12篇文章,我们来继续聊聊interface的使用. 在上一篇文章当中我们介绍了面向对象的一些基本概念,以及gol ...

  9. UI数据库

    一.数据库 SQL: SQL是Structured Query Language(结构化查询语言)的缩写.SQL是专为数据库而建立的操作命令集, 是一种功能齐全的数据库语言. 二.数据库管理系统 数据 ...

随机推荐

  1. 在Windows Server 2012中打开传统的磁盘管理界面

    在“运行”中输入diskmgmt.msc即可

  2. 关于p标签

    说p标签是不能嵌套div和p的,嵌套会被浏览器解析分离.但如果你使用了document.createElement创建div,再appendChild的话反而可以了.看来浏览器并不支持动态解析

  3. springCloud微服务入门

    目录 前言 Eureka 注册中心server 新建 配置 服务提供者service 新建 配置 服务消费者controller 新建 配置 使用 Feign负载均衡 前言 springCloud是一 ...

  4. Oracle EBS AR 删除应收发票

    DECLARE    -- Non-scalar parameters require additional processing     p_errors arp_trx_validate.mess ...

  5. 使用ModelForm表单验证

    1.定义model.py model中定义的字段类型,只有在通过form进行验证的时候才有效,数据库中的字段类型与其并不完全一致,如数据库中并没有ipaddress类型.如果不通过form对字段进行验 ...

  6. 铁乐学Python_day12_装饰器

    [函数的有用信息] 例: def login(user, pwd): ''' 功能:登录调用 参数:分别有user和pwd,作用分别是用户和密码: return: 返回值是登录成功与否(True,Fa ...

  7. 服务器安装LNMP及构建个人站点

    服务器安装LNMP(centos6.6+nginx1.7.12+mysql5.6.24+php5.6.7) 本次安装  centos6.6+nginx1.7.12+mysql5.6.24+php5.6 ...

  8. C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数

    各位相加 给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数. 示例: 输入: 输出: 解释: 各位相加的过程为: + = , + = . 由于 是一位数,所以返回 . 进阶:你可以 ...

  9. randint(1,100) s.add(n) 集合的去重复性

  10. PHP设计模式系列 - 数据访问对象模式

    数据访问对象模式 数据访问对象模式描述了如何创建透明访问数据源的对象. 场景设计 设计一个BaseDao基类,实现数据库操作基本的一些query,insert,update方法 在实际使用的过程中,继 ...