V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
yujianwjj
V2EX  ›  Go 编程语言

go 有关 nil 的一个疑问?

  •  
  •   yujianwjj · 2022-10-18 16:20:00 +08:00 · 3773 次点击
    这是一个创建于 769 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天突然意识到一个问题: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 的情况。

    36 条回复    2022-10-19 22:42:23 +08:00
    airplayxcom
        1
    airplayxcom  
       2022-10-18 16:26:06 +08:00   ❤️ 33
    会 go 不会 go 的都哑口无言了
    0o0O0o0O0o
        2
    0o0O0o0O0o  
       2022-10-18 16:26:50 +08:00 via iPhone
    在一些项目源码里见到过判断的,不过我是觉得没必要。
    randm
        3
    randm  
       2022-10-18 16:33:24 +08:00
    会报 panic
    coderxy
        4
    coderxy  
       2022-10-18 16:36:15 +08:00
    if a!=nil ?
    hsfzxjy
        5
    hsfzxjy  
       2022-10-18 16:38:21 +08:00 via Android
    这种一般是调用者保证它非 nil
    warlock
        6
    warlock  
       2022-10-18 16:38:36 +08:00
    这取决于哪里需要关心 a 为 nil 这件事
    charmToby
        7
    charmToby  
       2022-10-18 16:41:37 +08:00
    ```golang
    type A struct {
    a int
    }

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

    func NewA()*A{
    return &A{}
    }

    func TestA(t *testing.T) {
    //var a *A // 这一步只是声明了一个变量 a 为 结构体 A 指针类型,没有实例化
    //a.Run()
    a := NewA() // 一般都是这样去实例化
    a.Run()
    }
    ```
    XyIsMy
        8
    XyIsMy  
       2022-10-18 16:45:31 +08:00
    你这代码只是声明了 变量 a 是 A 类型。没有实例化
    fuxiaohei
        9
    fuxiaohei  
       2022-10-18 16:48:55 +08:00
    判不判断 nil 看需求,有时候结构体绑定的方法是当函数来用的
    ```golang

    import "fmt"

    type A struct {
    a int
    }

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

    func main() {
    var fn = (*A).Run
    fmt.Println(fn)
    fn(&A{111})
    }

    ```
    zhs227
        10
    zhs227  
       2022-10-18 16:50:51 +08:00
    变量没初始化不能怪函数绑定。
    pkoukk
        11
    pkoukk  
       2022-10-18 16:54:53 +08:00
    此时,a==nil
    所以,a.Run() == nil.Run()
    作为一个 nil,怎么可能执行的了(a *A) Run() 这个函数呢?
    这和 go 没有任何关系
    JKeita
        12
    JKeita  
       2022-10-18 17:02:30 +08:00
    你只是声明了个 A 指针类型变量,肯定会报错啊。
    tianyou666shen
        13
    tianyou666shen  
       2022-10-18 17:07:43 +08:00   ❤️ 3
    家人们 见鬼了
    声明变量但不初始化对象 居然不能调用对象方法
    CyJaySong
        14
    CyJaySong  
       2022-10-18 17:11:11 +08:00
    func TestA(t *testing.T) {
    var a = new(A)
    a.Run()
    }

    试试呢😜
    zhuweiyou
        15
    zhuweiyou  
       2022-10-18 17:22:19 +08:00
    要么 new, 要么 &A{} 吧
    siloong
        16
    siloong  
       2022-10-18 17:26:26 +08:00
    想到了开心的事

    ```golang
    type A struct{}

    func (*A) hello() {
    fmt.Println("hello, world")
    }

    func main() {
    var a *A
    fmt.Printf("a is nil? %v\n", a == nil)
    a.hello()
    }
    ```
    ArianX
        17
    ArianX  
       2022-10-18 17:29:09 +08:00
    @pkoukk 是可以执行的,a 是一个有类型的 nil ,可以调用这个类型的指针方法
    sardina
        18
    sardina  
       2022-10-18 17:32:31 +08:00   ❤️ 1
    我要调某个结构体的方法,但是我不初始化这个结构体,哎,我就是玩
    Citrus
        19
    Citrus  
       2022-10-18 17:33:03 +08:00
    @siloong 没毛病啊,你的 hello 又没用 A 里的东西,当然能跑。改成这样比较有意思:

    type A struct{}

    func (*A) hello() {
    fmt.Println("hello, world")
    }

    func main() {
    var a *A
    var b interface{}
    b = a
    fmt.Printf("a is nil? %v\n", a == nil)
    fmt.Printf("b is nil? %v\n", b == nil)
    a.hello()
    }
    pkoukk
        20
    pkoukk  
       2022-10-18 17:54:20 +08:00
    @ArianX 居然真的可以,涨知识了
    lxdlam
        21
    lxdlam  
       2022-10-18 17:58:55 +08:00   ❤️ 1
    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
        22
    chengyunbo  
       2022-10-18 18:08:04 +08:00
    蚌埠住了
    yougg
        23
    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
        24
    tairan2006  
       2022-10-18 20:16:16 +08:00
    go 的习惯一般是外围判断的,这算是一种约定
    joesonw
        25
    joesonw  
       2022-10-18 21:54:21 +08:00 via iPhone
    go 的方法你理解成 fun Run(a *A)就好了
    littlewing
        26
    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
        27
    Jaron0608  
       2022-10-18 22:29:34 +08:00
    这回复楼层不支持 markdown ,真是看的头皮发麻
    lanlanye
        28
    lanlanye  
       2022-10-18 23:33:56 +08:00
    虽然空值设计确实很麻烦,而且 Go 的 nil 有时候让人感到别扭,但你这个例子……

    我更想知道结构体里的 *bool 和 *string 这种东西有没有什么好的解决方案……
    AnroZ
        29
    AnroZ  
       2022-10-19 00:09:13 +08:00
    struct 指针判断是否为空还是很容易理解,毕竟 c/c++都是如此,golang 不显性的是 interface{} 对象无法通过 != nil 来直接判断是否为空,我也是写了好几个月代码后才发现。
    Trim21
        30
    Trim21  
       2022-10-19 00:11:54 +08:00
    你在(a *A) Run()里面也是可以判断 a 是不是 nil 的。
    colatin
        31
    colatin  
       2022-10-19 01:31:02 +08:00
    总有一个人要承担责任,显然调用者是最佳人选。
    iceheart
        32
    iceheart  
       2022-10-19 06:45:29 +08:00 via Android
    所以使用 this 指针的时候要不要判断它是不是 null?
    要啊! go 又没有异常处理,你得返回一个 error
    mango88
        33
    mango88  
       2022-10-19 09:15:15 +08:00
    @Jaron0608 可以看下这个 /t/873825
    Nzelites
        34
    Nzelites  
       2022-10-19 10:40:07 +08:00
    这个问题其实挺好玩的 理论上是可以弄成类似 static 函数的行为 但是 go 这么写显然会被打
    DefoliationM
        35
    DefoliationM  
       2022-10-19 11:21:34 +08:00
    可以调用方法,但是用了结构体里的东西,没初始化就 panic 了
    AmosAlbert
        36
    AmosAlbert  
       2022-10-19 22:42:23 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3416 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 11:48 · PVG 19:48 · LAX 03:48 · JFK 06:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.