go is an easy to learn language with purposefully limited features. but there are some lesser known features which you may not know about. i have tried to list a few of them here.

number literals

For readability, an underscore character may appear after a base prefix or between successive digits; such underscores do not change the literal's value.


prevent unkeyed literals

type X struct {
A, B int
_ struct{}

x := X{A: 1, B: 2} // OK
x := X{1, 2} // Error: too few values

note that also complains about unkeyed literals.

to prevent extra padding we can move the zero field to the top of the struct:

type X1 struct {
A, B int
_ struct{}

type X2 struct {
_ struct{}
A, B int

var (
x1 *X1
x2 *X2

"sizeof(X1) = %d sizeof(X2) = %d\n",
unsafe.Sizeof(*x1), unsafe.Sizeof(*x2)
) // sizeof(X1) = 24 sizeof(X2) = 16

type grouping

group syntax can also be used for types:

type (
T1 struct{}
T2 struct{}

pass multiple return value to another function

generally you can’t do this except for this special case:

if the return values of a function or method are equal in number and individually assignable to the parameters of another function or method , then the call will invoke after binding the return values of to the parameters of in order. The call of must contain no parameters other than the call of , and must have at least one return value.

func add2(x, y int) (int, int) {
return x+2, y+2
func add4(x, y int) (int, int) {
return x+4, y+4
func main() {
// OK x = 7, y = 8
x, y := add4(add2(1, 2))
// ERROR multiple-value add2() in single-value context
fmt.Printf("x = %d y = %d", add2(1, 2))

interface composition

interfaces can be union of other interfaces just like struct composition.

type I1 interface {
X() int

type I2 interface {

type I3 interface {
Z() int

methods with same name must have similar signatures.

type I1 interface {
X() int

type I2 interface {

type I3 interface {
I2 // ERROR: duplicate method 'X'

typed iota

you can specify a type for

const (
_ = iota
testvar // will be int


type myType int
const (
_ myType = iota
testvar // will be myType

type alias vs type definition

this is a type alias:

type SameMutex = sync.Mutex

this is a new type:

type NewMutex sync.Mutex

is just another name for and will have all the functionality of , but being a different type, does not inherit any methods bound to the given type:

m1 := SameMutex{}
m2 := NewMutex{}
m1.Lock() // OK
m2.Lock() // ERROR: Unresolved reference 'Lock'

package init()

a package may have one or multiple functions which are called during package initialization after all package-level variables are initialized.

importing a package for side effects runs this function:

import _ "net/http/pprof"

there are a few rules about the order of variable evaluation; in the sample code below, is initialized before so the output will be .

package mainvar X intfunc init() {
X = 1
var x2 = 2 * Xfunc main() {

special packages and directories

internal package is used to make specific packages unimportable. a package can be imported only by code in the directory tree rooted at . It cannot be imported by code in or in any other repository. this layout pattern is enforced on all repositories by go compiler since

you can use to put external package dependencies inside application directory. these dependencies can be managed manually or by .

The command will create the directory for you. Note that you might need to add the flag to your command if you are not using Go 1.14 where it's on by default.

some go tools like ignore this directory. others like and don’t. you can use a command like this if you wish to exclude this directory:

go list ./... | grep -v /vendor/ | xargs -L1 golint -set_exit_status


The go tool will ignore a directory named “testdata”, making it available to hold ancillary data needed by the tests.

when you run , for each package in scope, the test binary will be executed with its working directory set to the source directory of the package under test.

putting these together, locating test data from your test code is simply:


type hint

you can pass a zero value of a type to a function to inform it of the type you desire:

type A struct {
X string
Y string

func Decode(useThisType interface{}, binaryData []byte) interface{} {
json.Unmarshal(binaryData, useThisType)
return useThisType

func main() {
value := Decode(&A{}, []byte(`{"X":"10", "Y":"20"}`))

detailed output with %v

use %v of printf family functions to print data with sufficient details:

type A struct {
X string
Y string

func main() {
fmt.Printf("%v", A{X: "10", Y:"20"})
fmt.Printf("%+v", A{X: "10", Y:"20"})
fmt.Printf("%#v", A{X: "10", Y:"20"})


{10 20}
{X:10 Y:20}
main.A{X:"10", Y:"20"}

check this cheat sheet for other annotation verbs.

Example functions

func Example() {...}
func ExampleT() {...}

functions with these signatures can be used to place usage examples in godoc. Examples should be placed in a file with a _test suffix. you can document the output of the example by adding an output comment at its end.

func ExampleExamples_output() {
// Output: Hello

also, if output is provided will run the example and compares it’s output with the output specified in the comment section and report the example function as passed if they match. if no output is provided will only compile it.

error wrapping

since errors can wrap another error creating a hierarchy of errors.

directive in is used for wrapping an error:

e1 := errors.New("error one")
e2 := fmt.Errorf("e2 : %w", e1)


e2 : error one

use Unwrap() to unwrap errors:

e3 := errors.Unwrap(e2)


error one

check this post for a detailed explanation on error wrapping.

embed files

as of you can natively embed files using package

package main

import _ "embed"

func main() {
//go:embed "hello.txt"
var s string


Hello, Gophers!

read more about embedding here.

forcing a type to json marshal

the default type for integer values is and int64 values may overflow. use this syntax to force encoding into string:

type Data struct {
ID int64 `json:"id,string"`

block forever

you can use an empty select to block forever:

select {

a common use case is when you have a single server with multiple http listeners. you can spawn each was with a goroutine in and block forever using . this is also a good place to check os signals.

you can find other ways to block here.

comment magic

for most parts comments are just comments. but there are situations where comments may have some side effects, we have seen two of these situations so far (the embed command and example function output), here are other situations:

godoc text

godoc uses the comment block immediately before a declaration as the documentation of the declaration; the first line of the comment should start with the name of declaration:

// Object is an object
type Object struct {}

build constraints

a comment that adheres to the following rules is recognized as a build constraint by :

  • located at the start of the file
  • start with the prefix followed by one or more tags
// +build linux

this build tag tells the go compiler to use this file for linux only.

build tags can be combined with the following rules:

  • build tags starting with are negated
  • space-separated tags are logically OR’d
  • comma-separated tags and line-separated tags are logically AND’d

this constraint requires or :

// +build linux windows

this constraint requires both and architecture:

// +build linux,386

a full detail of build constraints can be found here.

go generate

when you run the command , the tool search for comments of the form . this command can be anything and doesn’t have any requirements.


Cgo allows Go programs to call C code directly. comment immediately preceding directive (AKA the preamble) will be treated as standard code and can the be accessed via the C package:

// #include <stdio.h>
// static void myprint(char *s) {
// printf("%s\n", s)
// }
import "C"

DNS specialist, Golang enthusiast