dark corners of go

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.
1_000_000
0_600
0x_42_af_3d
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 go vet
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
)
fmt.Printf(
"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 g
are equal in number and individually assignable to the parameters of another function or method f
, then the call f(g(parameters_of_g))
will invoke f
after binding the return values of g
to the parameters of f
in order. The call of f
must contain no parameters other than the call of g
, and g
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 {
Y()
}
type I3 interface {
I1
I2
Z() int
}
methods with same name must have similar signatures.
type I1 interface {
X() int
}
type I2 interface {
X()
}
type I3 interface {
I1
I2 // ERROR: duplicate method 'X'
}
typed iota
you can specify a type for iota
const (
_ = iota
testvar // will be int
)
vs
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
SameMutex
is just another name for Mutex
and will have all the functionality of Mutex
, but NewMutex
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 init()
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, x2
is initialized before init()
so the output will be 0
.
package mainvar X intfunc init() {
X = 1
}var x2 = 2 * Xfunc main() {
println(x2)
}
special packages and directories
/internal
internal package is used to make specific packages unimportable. a package .../a/b/c/internal/d/e/f
can be imported only by code in the directory tree rooted at .../a/b/c
. It cannot be imported by code in .../a/b/g
or in any other repository. this layout pattern is enforced on all repositories by go compiler since go 1.5
/vendor
you can use /vendor
to put external package dependencies inside application directory. these dependencies can be managed manually or by go mod
.
The go mod vendor
command will create the /vendor
directory for you. Note that you might need to add the -mod=vendor
flag to your go build
command if you are not using Go 1.14 where it's on by default.
some go tools like go list
ignore this directory. others like gofmt
and golint
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
testdata
The go tool will ignore a directory named “testdata”, making it available to hold ancillary data needed by the tests.
when you run go test
, 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:
os.Open("testdata/data.json")
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"}`))
fmt.Println(value)
}
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"})
}
output:
{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() {
fmt.Println("Hello")
// Output: Hello
}
also, if output is provided go test
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 go test
will only compile it.
error wrapping
since go 1.13
errors can wrap another error creating a hierarchy of errors.
%w
directive in fmt.Errorf
is used for wrapping an error:
e1 := errors.New("error one")
e2 := fmt.Errorf("e2 : %w", e1)
fmt.Println(e2)
output:
e2 : error one
use Unwrap() to unwrap errors:
e3 := errors.Unwrap(e2)
fmt.Println(e3)
output:
error one
check this post for a detailed explanation on error wrapping.
embed files
as of go 1.16
you can natively embed files using embed
package
package main
import _ "embed"
func main() {
//go:embed "hello.txt"
var s string
print(s)
}
output:
Hello, Gophers!
read more about embedding here.
forcing a type to json marshal
the default type for integer values is float64
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 main()
and block forever using select{}
. 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 go build
:
- located at the start of the file
- start with the prefix
+build
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 windows
or linux
:
// +build linux windows
this constraint requires both linux
and 386
architecture:
// +build linux,386
a full detail of build constraints can be found here.
go generate
when you run the command go generate
, the tool search for comments of the form //go:generate command arguments
. this command can be anything and doesn’t have any requirements.
cgo
Cgo allows Go programs to call C code directly. comment immediately preceding #import “C”
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"
C.myprint(C.String("foo"))