Based on images from gopherize.me
Quite often from Go code we have to work with various HTTP APIs or act as an HTTP service ourselves.
One of the most common cases: we receive data in the form of a structure from the database, send the structure to the external API, in response we receive another structure, somehow transform it and save it to the database.
In other words: such processing does not require many separate operations with the request and response structures.
API , , nil - -nil .
type ApiResponse struct {
Code *string json:"code"`
}
, , Go escape . β 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 .
Also, for the type, you will need to describe the encoding and decoding functions.
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
}
As a result of getting rid of reference types in the API - I developed a nan library , with basic Nullable types with encoding and decoding functions for JSON, jsoniter, easyjson, gocql.
Convenience of using Nullable types
And one of the last questions you can ask about switching to Nullable types is whether they are convenient to use.
My personal opinion is convenient, types have the same usage pattern as variable references.
When using a link, we write
if a != nil && *a == "sometext" {
With a Nullable type, we write
if a.Valid && a.String == "sometext" {