最近同事在设计 interceptor的时候, 发现 resp 通信层修改后, 并没有上传到 业务逻辑层, 后来发现是 调入了 golang 传值的陷阱中去了. 这里写一个例子一起review下golang传值的特点.


package main

import (
    "fmt"
)

type Message struct{
    Topic string
    Body []byte
    switcher bool
}

func doInt(resp interface{}) {
    resp = &Message{
        Topic: "hahha",
        Body: []byte("heiya heiya"),
    }
}

func doStruct(resp *Message) {
    resp = &Message{
        Topic: "hahha",
        Body: []byte("heiya heiya"),
    }
}

func doField(resp *Message) {
    resp.Topic = "haha"
    resp.Body = []byte("lalal")
    resp.switcher = true
}

func main() {
    r := new(Message)
    doInt(r)
    fmt.Printf("after interface, msg: %v\n", r)

    r1 := new(Message)
    doStruct(r1)
    fmt.Printf("after struct, msg: %v\n", r1)

    r2 := new(Message)
    doField(r2)
    fmt.Printf("after field. msg: %v\n", r2)
}

在运行后, 会惊讶的发现:

after interface, msg: &{ [] false}
after struct, msg: &{ [] false}
after field. msg: &{haha [108 97 108 97 108] true}

因为在两种场景中, 传参是直接指向了另一块内存, 根据传值引用的特性, 虽然修改了 传参, 但是因为是传值的行为, 所以并不会透传到 使用者。第三种情况, 虽然也是传值类型, 但是参数和入参都是指向同一块内存的, 所以, 这里的修改, 实际上修改的是内存地址上的值, 因此使用者是可以感知的.