go 有关 nil 的一个疑问?

2022-10-18 16:20:00 +08:00
 yujianwjj

今天突然意识到一个问题:go 的代码中,关于调用结构体指针绑定的方法的时候,方法里面似乎都没有判断该指针是否为 nil 的情况。

type A struct {
	a int
}

func (a *A) Run() {
	fmt.Print(a.a)
}

func TestA(t *testing.T) {
	var a *A
	a.Run()
}

以上代码会报错,原因也很明显,a 为 nil, a.Run() 方法中,访问 a.a 报错。 看了一下自己之前写的所有 go 代码,所有 结构体指针绑定的方法里面,都没有判断是否为 nil 的情况。

3571 次点击
所在节点    Go 编程语言
36 条回复
lxdlam
2022-10-18 17:58:55 +08:00
Go 实际上编译生成的函数只是在语义层面上跟原始类型绑定,而在之后的代码生成过程中,Go 隐式生成了一个对应的函数,这个新的函数跟老的类型是解耦的,并把对象本身作为第一个参数传入。

举个例子来说,考虑你的测试代码,Go 编译器会生成下面的 stub:

```go
func (*A).Run(a *A) {
fmt.Print(a.a)
}
```

这种函数签名是你不能写出来的,因为在用户层面这种语法是 invalid 的,但是你可以调用。尝试在你的 Test 中插入:

```go
func TestA(t *testing.T) {
var a *A
a.Run()
}

func TestAnother(t *testing.T) {
var a *A
(*A).Run(a)
}
```

代码可以正常编译执行,然后报错。

而假如我定义另一个方法,不访问 A 的内部参数,即不依赖 A 的值,你会发现代码能够安全运行,没有任何问题( 16 楼老哥已经有 runnable 了)。

这种情况是不是比较反直觉?而实际上 GCC 和 clang 也是这么做的: https://godbolt.org/z/xb9WWz53x 。当然,如果你打开 undefined behavior santizer ,你会发现编译器报错了,因为标准并没有对这个情况作出规定。

所以是否需要检查 receiver 是不是 nil ?取决于你。如果这个方法你不想 panic ,那你可以检查 `if p != nil` 并做出恰当报错,但是这会影响大家对于 method call 语义的共识理解(如果 a 为 nil ,我调用它的方法应该 panic 啊?为什么没有 panic ?)。或者直接 let it crash ,也方便查错。
chengyunbo
2022-10-18 18:08:04 +08:00
蚌埠住了
yougg
2022-10-18 20:06:15 +08:00
```go
// 以下是定义在结构体值上的方法原型,通过调用结构体类型上定义的函数,传入结构体的值
A.echo_A(a) // (_ A)
A.echoA(a, "a") // (A) a
// A.echo_жA(a) // A.echo_жA 未定义
// A.echoжA(a) // A.echoжA 未定义
A.setX(a, 4)
// A.setY(a, 7) // A.setY 未定义
println(a.x) // 0
```

给你参考一下:
https://github.com/yougg/gonote/blob/main/gogrammar.md#%E6%96%B9%E6%B3%95-method
tairan2006
2022-10-18 20:16:16 +08:00
go 的习惯一般是外围判断的,这算是一种约定
joesonw
2022-10-18 21:54:21 +08:00
go 的方法你理解成 fun Run(a *A)就好了
littlewing
2022-10-18 21:59:51 +08:00
```cpp

class A {
public:
void f1() { cout << "foobar " << this->a;}

private:
int a;
};

int main() {
A *a = nullptr;
a->f1();
}
```

也不需要判断 this 是不是 NULL 啊,这样是不是好理解多了
Jaron0608
2022-10-18 22:29:34 +08:00
这回复楼层不支持 markdown ,真是看的头皮发麻
lanlanye
2022-10-18 23:33:56 +08:00
虽然空值设计确实很麻烦,而且 Go 的 nil 有时候让人感到别扭,但你这个例子……

我更想知道结构体里的 *bool 和 *string 这种东西有没有什么好的解决方案……
AnroZ
2022-10-19 00:09:13 +08:00
struct 指针判断是否为空还是很容易理解,毕竟 c/c++都是如此,golang 不显性的是 interface{} 对象无法通过 != nil 来直接判断是否为空,我也是写了好几个月代码后才发现。
Trim21
2022-10-19 00:11:54 +08:00
你在(a *A) Run()里面也是可以判断 a 是不是 nil 的。
colatin
2022-10-19 01:31:02 +08:00
总有一个人要承担责任,显然调用者是最佳人选。
iceheart
2022-10-19 06:45:29 +08:00
所以使用 this 指针的时候要不要判断它是不是 null?
要啊! go 又没有异常处理,你得返回一个 error
mango88
2022-10-19 09:15:15 +08:00
@Jaron0608 可以看下这个 /t/873825
Nzelites
2022-10-19 10:40:07 +08:00
这个问题其实挺好玩的 理论上是可以弄成类似 static 函数的行为 但是 go 这么写显然会被打
DefoliationM
2022-10-19 11:21:34 +08:00
可以调用方法,但是用了结构体里的东西,没初始化就 panic 了
AmosAlbert
2022-10-19 22:42:23 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/887817

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX