Golang 不可寻址的理解

Table of Contents

如果没有听过 Golang 的不可寻址(not addressable)相关概念,没有关系,从字面上看,就是无法获取值的地址。先举个简单的例子:

package main

import "fmt"

func main() {
    m := map[int]string{0: "hello"}
    fmt.Printf("%p", &m[0])
}

mmap[int]string 类型,只包含一个键值对,打印这个键值对的值的地址,直接编译错误:cannot take the address of m[0],无法获取 map 中值的地址。这是个比较常见的不可寻址的例子。原因也比较简单,map 类型是通过哈希表实现的,随着 map 元素的增多,可能触发扩容,那么 map 的值的位置发生改变,即其地址会发生变化,所以无法对 map 的值寻址。另一方面,如果元素不存在于map 中,返回零值,而零值是不可变对象,是不能寻址的(golang 中不可变对象是不可寻址的,如常量)。

再看个稍微复杂的例子,在Golang wiki 的 MethodSets 有这样一句话:

The concrete value stored in an interface is not addressable, in the same way that a map element is not addressable.

意思具体值赋值给 interface 类型后与 map 中的元素一样是不可寻址的。先不去理解这句话,先看个例子:

package main

const NewName = "run.wu"

type Male struct {
	Name string
}

func (m Male) getName() string {
	return m.Name
}

func (m *Male) setName(name string) {
	m.Name = name
}

func main() {
	m1 := Male{}
	m1.getName()
	m1.setName(NewName)

	m2 := &Male{}
	m2.getName()
	m2.setName(NewName)
}

定义了一个 Male 类型,有两个方法,一个是值接收者的 getName() 方法,一个是指针接收者 setName() 方法。在 main() 中,m1Male 类型的值对象,m2Male 类型的指针对象,在这两个对象上都调用了 getName()setName() 方法,可以正常调用执行。那么问题来了:

  • m1 是值类型对象(调用者),为什么可以调用指针接收者方法 setName()
  • m2 是指针类型对象(调用者),什么可以调用值接收者方法 getName()?

对于第一个问题,值调用者调用指针接收者方法时, 编译器默认会使用调用者的引用(取地址)来调用方法,即编译器隐示转换 成 (&m1).setName() 对于第二个问题,指针调用者调用值接收者方法时,编译器默认会将指针调用者解引用(取值)为值类型,即编译器隐示转换成 (*m2).getName()

上面的例子还是比较好理解的,很多书和文章都有解释这个问题。

那再看另一个例子:

package main

const NewName = "run.wu"

type Person interface {
	getName() string
	setName(name string)
}

type Male struct {
	Name string
}

func (m Male) getName() string {
	return m.Name
}

func (m *Male) setName(name string) {
	m.Name = name
}

func main() {
	var p1 Person = Male{} // error
	p1.getName()
	p1.setName(NewName)

	var p2 Person = &Male{}
	p2.getName()
	p2.setName(NewName)
}

这个例子和刚刚的例子很类似,不同点的是,增加了一个 Person 类型的接口,定义了 Male 类型,实现了值接收者的 getName() 方法,实现了指针接收者的 setName() 方法。

初始化 Male 的值对象赋值给 Person 接口,记作 p1,直接报错:

cannot use Male literal (type Male) as type Person in assignment:
	Male does not implement Person (setName method has pointer receiver)

错误内容是,Male 类型的变量不能赋值给 Person,因为 Male 类型没有实现 Person 接口(setName 是指针接收者方法)。

那为什么之前的例子中,编译器可以自动将值类型(非接口类型)取地址做隐示转换,而这里就不可以了?原因就是开头那就话:

The concrete value stored in an interface is not addressable.

值类型赋值给接口,是不可寻址的,既然不可寻址,编译器也就没办法自动取其地址传给指针接收的方法了。