I have just started learning Go and found it to be a very interesting language. It bridges the gap between rapid development and performance by offering high performance like C, C++ along with rapid development like Ruby, Python.
Through this blog, I wanted to share some behaviours of Go that i found tricky along with some style guidelines.
UN-EXPORTED FIELDS IN STRUCT CAN BE A MYSTERY
Yes it was a mystery for me when I started. My use case was simple, I was having an object of Person struct and wanted to marshal it using encoding/json
package.
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
name string
age int
}
func main() {
p := Person{name: "Anuj Verma", age: 25}
b, err := json.Marshal(p)
if err != nil {
fmt.Printf("Error in marshalling: %v", err)
}
fmt.Println(string(b))
}
Output:
{}
Oh things worked fine without any error. But wait, why is the response empty ? I thought it must be some typo. I checked and checked and checked…
I had no idea why things were not working. Then I asked to every developer’s god(Google). You will not believe me but this was the first time I understood the real importance of exported and un-exported identifiers
in Go.
Since encoding/json
is a package outside main and the fields inside our struct name
and age
are un-exported (i.e begins with a small case), therefore encoding/json
package does not have access to Person struct fields and it cannot marshal it.
So to solve this problem, I renamed fields of Person struct to Name
, Age
and it worked like a charm. Check here
JSON.DECODE VS JSON.UNMARSHAL ?
I was once writing an application which makes HTTP call to Github api. The api response was in JSON format.
So to receive the response and use it I created a Go struct (GithubResponse) matching the format of API response. The next step was to deserialise it. After looking up from the internet I came up with two possible ways to do it.
var response GithubResponse
err = json.NewDecoder(req.Body).Decode(&response)
var response GithubResponse
bodyBytes, _ := ioutil.ReadAll(req.Body)
err := json.Unmarshal(bodyBytes, &response)
Both will exactly do the same thing and will de-serialise a JSON payload to our go struct. So I was confused about which one to use ? After some research I was surprised to know that using json.Decode
to de-serialise a JSON response is not a recommended way. It is not recommended because it is designed explicitly for JSON streams.
I have heard JSON, what is JSON stream ?
Example JSON:
{
"total_count": 3,
"items": [
{
"language": "ruby"
},
{
"language": "go"
}
]
}
Example JSON stream:
{"language": "ruby"}
{"language": "go"}
{"language": "c"}
{"language": "java"}
So JSON streams are just JSON objects concatenated. So if you have a use case where you are streaming structured data live from an API, then you should go for json.Decode
. As it has the ability to de-serialise an input stream.
If you are working with single JSON object at a time (like our example json shown above), go for json.Unmarshal
.
VAR DECLARATION VS :=
So this one is just a cosmetic suggestion, Remember when declaring a variable which does not needs an initial value prefer:
var list []string
over
list := []string{}
There’s no difference between them, except that the former may be used at package level (i.e. outside a function), but the latter may not. But still if you are inside a function where you have the choice to use both, It is a recommended style to use the former one.
Rule of thumb is to avoid using shorthand syntax if you are not initialising a variable.
IMPORTS USING BLANK IDENTIFIER
In one of my application we are using postgres database. I am using “lib/pq” which is a go postgres driver for database. I was going through the documentation here and I saw this:
import (
"database/sql"
_ "github.com/lib/pq"
)
func main() {
connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
}
Is this correct ? Why are we using an underscore
in front of a package import. Checking on the internet I found that it is an anonymous import. It will import the package, but not give you access to the exported entities.
So the next question is very obvious:
If I do not have access to package entities, why we are importing it?
You remember when I said Go is an interesting language. In Go we can define an init()
function in each source file, which allows us to setup things before the program executes. So sometimes we need to import a package so that its init() function gets called, without using the package directly in code.
Now lets understand why in code snippet above github.com/lib/pq
is imported as a blank identifier. Package database/sql
has a function
func Register(name string, driver driver.Driver)
which needs to be called to register a driver for database. If you have a look at this line from lib/pq
library, things become more clearer. So lib/pq
is calling the Register
function to register an appropriate database driver even before our main function executes.
So even we are not using lib/pq
directly from our code, but we need it to register driver postgres
before calling sql.Open()
.
USE SHORTER VARIABLE NAMES IN LIMITED SCOPE
In most of the languages you might have observed that it is advised to use descriptive variable names. For example use index instead of i. But in Go it is advised to use shorter variable names for variables with limited scopes.
For a method receiver, one or two letters is sufficient. Common variables such as loop indices and readers can be a single letter (i
, r
). More unusual things and global variables need more descriptive names.
Rule of thumb is:
The further from its declaration that a name is used, the more descriptive the name must be.
Examples:
Good Style
// Global Variable: Use descriptive name as it can be used anywhere in file
var shapesMap map[string]interface{}
// Method
// c for receiver is fine because it has limited scope
// r for radius is also fine
func(c circle) Area(r float64) float64 {
return math.Pi * r * r
}
EXPLICITLY IGNORE A JSON FIELD
If you want to ignore a field of struct while serialising/de-serialising a json, you can use json:"-"
. Have a look at an example below:
type Person struct {
ID int `json:"-"`
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}
In above struct ID field will be ignored while serialising/de-serialising.
BACKQUOTES TO THE RESCUE
The back quotes are used to create raw string literals which can contain any type of character. So if you want to create a multi line string in Go, you can use back quotes. This will help you to save the effort for using escape characters inside string.
For example, suppose you want to define a string containing a JSON body:
{"name": "anuj verma", "age": 25}
See the below two ways:
b := "{\"name\": \"anuj verma\", \"age\": 25}"// Bad Style
b := `{"name": "anuj verma", "age": 25}`// Good Style
CONCLUSION
What am I missing here? Let me know in the comments and I’ll add it in. If you enjoyed this post, I’d be very grateful if you’d help it spread by sharing. Thank you.
Like this:
Like Loading...
Anuj Verma
Share post:
I have just started learning Go and found it to be a very interesting language. It bridges the gap between rapid development and performance by offering high performance like C, C++ along with rapid development like Ruby, Python.
Through this blog, I wanted to share some behaviours of Go that i found tricky along with some style guidelines.
UN-EXPORTED FIELDS IN STRUCT CAN BE A MYSTERY
Yes it was a mystery for me when I started. My use case was simple, I was having an object of Person struct and wanted to marshal it using
encoding/json
package.Output:
Oh things worked fine without any error. But wait, why is the response empty ? I thought it must be some typo. I checked and checked and checked…
I had no idea why things were not working. Then I asked to every developer’s god(Google). You will not believe me but this was the first time I understood the real importance of
exported and un-exported identifiers
in Go.Since
encoding/json
is a package outside main and the fields inside our structname
andage
are un-exported (i.e begins with a small case), thereforeencoding/json
package does not have access to Person struct fields and it cannot marshal it.So to solve this problem, I renamed fields of Person struct to
Name
,Age
and it worked like a charm. Check hereJSON.DECODE VS JSON.UNMARSHAL ?
I was once writing an application which makes HTTP call to Github api. The api response was in JSON format.
So to receive the response and use it I created a Go struct (GithubResponse) matching the format of API response. The next step was to deserialise it. After looking up from the internet I came up with two possible ways to do it.
Both will exactly do the same thing and will de-serialise a JSON payload to our go struct. So I was confused about which one to use ? After some research I was surprised to know that using
json.Decode
to de-serialise a JSON response is not a recommended way. It is not recommended because it is designed explicitly for JSON streams.I have heard JSON, what is JSON stream ?
Example JSON:
Example JSON stream:
So JSON streams are just JSON objects concatenated. So if you have a use case where you are streaming structured data live from an API, then you should go for
json.Decode
. As it has the ability to de-serialise an input stream.If you are working with single JSON object at a time (like our example json shown above), go for
json.Unmarshal
.VAR DECLARATION VS :=
So this one is just a cosmetic suggestion, Remember when declaring a variable which does not needs an initial value prefer:
over
There’s no difference between them, except that the former may be used at package level (i.e. outside a function), but the latter may not. But still if you are inside a function where you have the choice to use both, It is a recommended style to use the former one.
IMPORTS USING BLANK IDENTIFIER
In one of my application we are using postgres database. I am using “lib/pq” which is a go postgres driver for database. I was going through the documentation here and I saw this:
Is this correct ? Why are we using an
underscore
in front of a package import. Checking on the internet I found that it is an anonymous import. It will import the package, but not give you access to the exported entities.So the next question is very obvious:
If I do not have access to package entities, why we are importing it?
You remember when I said Go is an interesting language. In Go we can define an
init()
function in each source file, which allows us to setup things before the program executes. So sometimes we need to import a package so that its init() function gets called, without using the package directly in code.Now lets understand why in code snippet above
github.com/lib/pq
is imported as a blank identifier. Packagedatabase/sql
has a functionwhich needs to be called to register a driver for database. If you have a look at this line from
lib/pq
library, things become more clearer. Solib/pq
is calling theRegister
function to register an appropriate database driver even before our main function executes.So even we are not using
lib/pq
directly from our code, but we need it to register driverpostgres
before callingsql.Open()
.USE SHORTER VARIABLE NAMES IN LIMITED SCOPE
In most of the languages you might have observed that it is advised to use descriptive variable names. For example use index instead of i. But in Go it is advised to use shorter variable names for variables with limited scopes.
For a method receiver, one or two letters is sufficient. Common variables such as loop indices and readers can be a single letter (
i
,r
). More unusual things and global variables need more descriptive names.Rule of thumb is:
Examples:
Good Style
EXPLICITLY IGNORE A JSON FIELD
If you want to ignore a field of struct while serialising/de-serialising a json, you can use
json:"-"
. Have a look at an example below:In above struct ID field will be ignored while serialising/de-serialising.
BACKQUOTES TO THE RESCUE
The back quotes are used to create raw string literals which can contain any type of character. So if you want to create a multi line string in Go, you can use back quotes. This will help you to save the effort for using escape characters inside string.
For example, suppose you want to define a string containing a JSON body:
See the below two ways:
CONCLUSION
What am I missing here? Let me know in the comments and I’ll add it in. If you enjoyed this post, I’d be very grateful if you’d help it spread by sharing. Thank you.
Share this:
Like this:
Simple and Powerful ReverseProxy in Go
In this article we will learn about reverse proxy, where to use it and how to implement it in Golang. A reverse proxy is a server that sits in front. read more…
Share this:
Like this:
Continue Reading