Go:如何在不使用引用类型的情况下使用nil值



基于来自gopherize.me的图像



通常,从Go代码中,我们必须使用各种HTTP API或自己充当HTTP服务。



最常见的情况之一:我们从数据库接收结构形式的数据,将结构发送到外部API,作为响应,我们接收到另一个结构,以某种方式对其进行转换并将其保存到数据库中。



换句话说:这种处理不需要对请求和响应结构进行许多单独的操作。



API通常在请求和响应结构中具有可以为nil并且可以采用某些非nil值的字段。这样的结构通常看起来像这样



type ApiResponse struct {
  Code *string json:"code"`
}


并且,由于它是引用类型,因此Go编译器可以进行转义分析,并且可以将给定的变量传输到堆中。在频繁创建此类变量的情况下,如果GC没有时间释放所有已使用的内存,我们会给GC带来额外的负担,甚至会出现“内存泄漏”。



在这种情况下可以做什么:



  • 修改外部API以不使用nil值。有时这是可以接受的,但是更改API并不总是一个好主意:首先,这是额外的工作,其次,这种返工可能会出现错误。
  • 修改Go代码,以便我们可以接受nil值,但不要为此使用引用类型。


, " "



.



Go



type pointerSmall struct {
 Field000 *string
 Field001 *string
 Field002 *string
 Field003 *string
 Field004 *string
 Field005 *string
}


,



type valueSmall struct {
 Field000 string
 Field001 string
 Field002 string
 Field003 string
 Field004 string
 Field005 string
}


0 , .

, .



: Go, ( - ) .



— . , . . — . , .. Go .



— , . , .



BenchmarkPointerSmall-8    1000000000          0.295 ns/op        0 B/op        0 allocs/op
BenchmarkValueSmall-8      184702404          6.51 ns/op        0 B/op        0 allocs/op


. , - - .



BenchmarkPointerSmallChain-8    1000000000          0.297 ns/op        0 B/op        0 allocs/op
BenchmarkValueSmallChain-8      59185880         20.3 ns/op        0 B/op        0 allocs/op


JSON . , jsoniter. . , .



BenchmarkPointerSmallJSON-8       49522      23724 ns/op    14122 B/op       28 allocs/op
BenchmarkValueSmallJSON-8         52234      22806 ns/op    14011 B/op       15 allocs/op


, easyjson. , .



BenchmarkPointerSmallEasyJSON-8       64482      17815 ns/op    14591 B/op       21 allocs/op
BenchmarkValueSmallEasyJSON-8         63136      17537 ns/op    14444 B/op       14 allocs/op


: , . (/ ) — .



.



type pointerBig struct {
 Field000 *string
 ...
 Field999 *string
}

type valueBig struct {
 Field000 string
 ...
 Field999 string
}


. , 0 , ( , .. ). , :



BenchmarkPointerBig-8       36787      32243 ns/op    24192 B/op     1001 allocs/op
BenchmarkValueBig-8        721375       1613 ns/op        0 B/op        0 allocs/op


. . ( , ).



BenchmarkPointerBigChain-8       36607      31709 ns/op    24192 B/op     1001 allocs/op
BenchmarkValueBigChain-8        351693       3216 ns/op        0 B/op        0 allocs/op


.



BenchmarkPointerBigJSON-8         250    4640020 ns/op  5326593 B/op     4024 allocs/op
BenchmarkValueBigJSON-8           270    4289834 ns/op  4110721 B/op     2015 allocs/op


, easyjson. . , jsoniter.



BenchmarkPointerBigEasyJSON-8         364    3204100 ns/op  2357440 B/op     3066 allocs/op
BenchmarkValueBigEasyJSON-8           380    3058639 ns/op  2302248 B/op     1063 allocs/op


: — , . — " ". (easyjson ), — .





— Nullable . sql — sql.NullBool, sql.NullString .



另外,对于类型,您将需要描述编码和解码功能。



func (n NullString) MarshalJSON() ([]byte, error) {
    if !n.Valid {
        return []byte("null"), nil
    }

    return jsoniter.Marshal(n.String)
}

func (n *NullString) UnmarshalJSON(data []byte) error {
    if bytes.Equal(data, []byte("null")) {
        *n = NullString{}
        return nil
    }

    var res string

    err := jsoniter.Unmarshal(data, &res)
    if err != nil {
        return err
    }

    *n = NullString{String: res, Valid: true}

    return nil
}


由于摆脱了API中的引用类型,我开发了一个nan库该库具有基本的Nullable类型,这些类型具有JSON,jsoniter,easyjson和gocql的编码和解码功能。



使用Nullable类型的便利



关于切换到Nullable类型,您可能要问的最后一个问题是使用它们是否方便。



我个人认为很方便,类型与变量引用具有相同的用法模式。



当使用链接时,我们写



if a != nil && *a == "sometext" {


使用Nullable类型,我们写



if a.Valid && a.String == "sometext" {



All Articles