[Golang] 反序列化中的隐式转换设计问题讨论 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Charlie17Li

[Golang] 反序列化中的隐式转换设计问题讨论

  •  
  •   Charlie17Li 2025 年 3 月 22 日 2529 次点击
    这是一个创建于 397 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    在反序列化的时候,需要一个 interface 用来接收反序列化的内容,如下代码所示

    func doNormal(data []byte) (*Student, error) { s := &Student{} if err := json.Unmarshal(data, s); err != nil { return nil, err } return s, nil } 

    然而为了使代码更加简化,习惯性得使用了命名返回值,导致反序列化失败

    func doBug(data []byte) (s *Student, _ error) { return s, json.Unmarshal(data, s) } 

    原因是因为 json.Unmarshal 中发现 s 是一个 nil ,直接 return error, 但将参数改成二级指针就 work 了

    func doSimple(data []byte) (s *Student, _ error) { return s, json.Unmarshal(data, &s) } 

    于是有了几个疑问:

    1. 这里二级指针能 work 的原因是啥?
    2. 这里二级指针指向的也是一个 nil 的一级指针,它能 work ,为啥直接使用一个 nil 的一级指针不行,这样设计的原因是啥?

    开始分析

    通过分析 json.Unmarshal 的代码可以发现 indirect 函数,这个函数会将二级指针反解成一级指针,并且发现一级指针是 nil 的时候,会初始化一个 Student

    大概过程是如下所示

    var p * Student var pp ** Student = &p v := reflect.ValueOf(pp).Elem() // v is nil v.Set(reflect.New(v.Type().Elem())) // v is not nil 

    问题

    1. 使用二级指针的方式还有啥坑吗,大家是怎么简化反/序列化代码的呢?
    2. 这里二级指针指向的也是一个 nil 的一级指针,而直接使用一个 nil 的一级指针就直接报错,这样设计的原因是啥?

    完整的 indirect 代码如下

    // indirect walks down v allocating pointers as needed, // until it gets to a non-pointer. // If it encounters an Unmarshaler, indirect stops and returns that. // If decodingNull is true, indirect stops at the first settable pointer so it // can be set to nil. func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { // Issue #24153 indicates that it is generally not a guaranteed property // that you may round-trip a reflect.Value by calling Value.Addr().Elem() // and expect the value to still be settable for values derived from // unexported embedded struct fields. // // The logic below effectively does this when it first addresses the value // (to satisfy possible pointer methods) and continues to dereference // subsequent pointers as necessary. // // After the first round-trip, we set v back to the original value to // preserve the original RW flags contained in reflect.Value. v0 := v haveAddr := false // If v is a named type and is addressable, // start with its address, so that if the type has pointer methods, // we find them. if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() { haveAddr = true v = v.Addr() } for { // Load value from interface, but only if the result will be // usefully addressable. if v.Kind() == reflect.Interface && !v.IsNil() { e := v.Elem() if e.Kind() == reflect.Pointer && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Pointer) { haveAddr = false v = e continue } } if v.Kind() != reflect.Pointer { break } if decodingNull && v.CanSet() { break } // Prevent infinite loop if v is an interface pointing to its own address: // var v interface{} // v = &v if v.Elem().Kind() == reflect.Interface && v.Elem().Elem() == v { v = v.Elem() break } if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) } if v.Type().NumMethod() > 0 && v.CanInterface() { if u, ok := v.Interface().(Unmarshaler); ok { return u, nil, reflect.Value{} } if !decodingNull { if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { return nil, u, reflect.Value{} } } } if haveAddr { v = v0 // restore original value after round-trip Value.Addr().Elem() haveAddr = false } else { v = v.Elem() } } return nil, nil, v } 
    8 条回复    2025-03-23 16:29:58 +08:00
    PTLin
        1
    PTLin  
       2025 年 3 月 22 日
    印象里 go 的命名返回值会带来一系列奇葩问题,在我眼里都属于语言层面的设计失误了,属于能不用就不用的东西。
    Trim21
        2
    Trim21  
       2025 年 3 月 22 日
    命名返回值确实挺奇葩,但这不是命名返回值带来的奇葩问题之一...

    这里问题是问题是,你 doBug 里的 nil 指针是 copy 进去... Unmarshal 内部就算能 new 一个 Student 出来,他要怎么修改你 doBug 里面指针指向的值呢?
    leonshaw
        3
    leonshaw  
       2025 年 3 月 22 日   1
    参数是传值的,函数无法改变传入变量本身的值。

    另外:

    > return s, json.Unmarshal(data, &s)

    不要这样写,求值顺序有问题 https://groups.google.com/g/golang-nuts/c/Q7KVGTFt3nU/m/WgnbugtwDAAJ
    ninjashixuan
        4
    ninjashixuan  
       2025 年 3 月 22 日
    div class="reply_content">基本不用命名返回值,除非是在 defer 里用到才会考虑。
    liyunlong41
        5
    liyunlong41  
       2025 年 3 月 23 日 via iPhone
    用正常写法,别玩花哨的,为你好也为别人好。
    voidmnwzp
        6
    voidmnwzp  
       2025 年 3 月 23 日 via iPhone
    unmarshal 调用方必须传入指针类型是因为 unmarshal 内部需要写入指针对应的内存,这样反序列化完成后,调用能找到对应的内存,而 nil 不指向任何内存
    lovelylain
        7
    lovelylain  
       2025 年 3 月 23 日 via Android
    return s, json.Unmarshal(data, s) 值传递,你传个 nil 的 s 进去,就算内部给你 new 了一个对象,也没法影响你这个 s 还是 nil
    Charlie17Li
        8
    Charlie17Li  
    OP
       2025 年 3 月 23 日
    @Trim21
    @voidmnwzp
    @lovelylain
    @leonshaw

    豁然开朗,把最基本的原理给忽视了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3929 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 68ms UTC 04:18 PVG 12:18 LAX 21:18 JFK 00:18
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86