28

Go 挖坑指南: “cannot take the address of XXX” and “cannot call pointer method on...

 4 years ago
source link: https://shockerli.net/post/golang-faq-cannot-take-the-address/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

先看代码

package main

type B struct {
    Id int
}

func New() B {
    return B{}
}

func New2() *B {
    return &B{}
}

func (b *B) Hello() {
    return
}

func (b B) World() {
    return
}

func main() {
    // 方法的接收器为 *T 类型
    New().Hello() // 编译不通过

    b1 := New()
    b1.Hello() // 编译通过

    b2 := B{}
    b2.Hello() // 编译通过

    (B{}).Hello() // 编译不通过
    B{}.Hello()   // 编译不通过

    New2().Hello() // 编译通过

    b3 := New2()
    b3.Hello() // 编译通过

    b4 := &B{} // 编译通过
    b4.Hello() // 编译通过

    (&B{}).Hello() // 编译通过

    // 方法的接收器为 T 类型
    New().World() // 编译通过

    b5 := New()
    b5.World() // 编译通过

    b6 := B{}
    b6.World() // 编译通过

    (B{}).World() // 编译通过
    B{}.World()   // 编译通过

    New2().World() // 编译通过

    b7 := New2()
    b7.World() // 编译通过

    b8 := &B{} // 编译通过
    b8.World() // 编译通过

    (&B{}).World() // 编译通过
}

输出结果

./main.go:25:10: cannot call pointer method on New()
./main.go:25:10: cannot take the address of New()
./main.go:33:10: cannot call pointer method on B literal
./main.go:33:10: cannot take the address of B literal
./main.go:34:8: cannot call pointer method on B literal
./main.go:34:8: cannot take the address of B literal

问题总结

假设 T 类型的方法上接收器既有 T 类型的,又有 *T 指针类型的,那么就不可以在不能寻址的 T 值上调用 *T 接收器的方法

&B{}
B{}
b := B{}

延伸思考

Go 语言规范 中规定了可寻址(addressable)对象的定义:

For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal. If the evaluation of x would cause a run-time panic, then the evaluation of &x does too.

对于类型为 T 的操作数 x ,地址操作符 &x 将生成一个类型为 *T 的指针指向 x 。操作数必须可寻址,即,变量、间接指针、切片索引操作,或可寻址结构体的字段选择器,或可寻址数组的数组索引操作。作为可寻址性要求的例外, x 也可为(圆括号括起来的)复合字面量。如果对 x 的求值会引起运行时恐慌,那么对 &x 的求值也会引起恐慌。

For an operand x of pointer type *T, the pointer indirection *x denotes the variable of type T pointed to by x. If x is nil, an attempt to evaluate *x will cause a run-time panic.

对于指针类型为 *T 的操作数 x ,间接指针 *x 表示类型为 T 的值指向 x 。若 xnil ,尝试求值 *x 将会引发运行时恐慌。

以下几种是可寻址的:

&x
&*x
&s[1]
&point.X
&a[0]
&struct{ X int }{1}

下列情况 x 是不可以寻址的,不能使用 &x 取得指针:

  • 字符串中的字节
  • map 对象中的元素
  • 接口对象的动态值(通过 type assertions 获得)
  • 常数
  • literal 值(非 composite literal)
  • package 级别的函数
  • 方法 method(用作函数值)
  • 中间值(intermediate value):
    • 函数调用
    • 显式类型转换
    • 各种类型的操作(除了指针引用 pointer dereference 操作 *x ):
      • channel receive operations
      • sub-string operations
      • sub-slice operations
      • 加减乘除等运算符

有几个点需要解释下:

  • 常数为什么不可以寻址? > 如果可以寻址的话,我们可以通过指针修改常数的值,破坏了常数的定义。

  • map 的元素为什么不可以寻址? > 两个原因,如果对象不存在,则返回零值,零值是不可变对象,所以不能寻址,如果对象存在,因为 Go 中 map 实现中元素的地址是变化的,这意味着寻址的结果是无意义的。

  • 为什么 slice 不管是否可寻址,它的元素读是可以寻址的? > 因为 slice 底层实现了一个数组,它是可以寻址的。

  • 为什么字符串中的字符/字节又不能寻址呢? > 因为字符串是不可变的。

规范中还有几处提到了 addressable:

  • 调用一个接收者为指针类型的方法时,使用一个可寻址的值将自动获取这个值的指针
  • ++-- 语句的操作对象必须可寻址或者是 map 的索引操作
  • 赋值语句 = 的左边对象必须可寻址,或者是 map 的索引操作,或者是 _
  • 上条同样使用 for ... range 语句

参考资料


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK