Go Programming Tips
Add 0x prefix when printing hex numbers
Use %#x
placeholder.
1 | func TestPrintHex() { |
1 | 123456 |
Print the type of a variable
1 | fmt.Printf("%T\n", i) |
Get random numbers
Intn()
returns the next integer in the current random sequence. UnixNano()
returns the number of nanoseconds elapsed since January 1, 1970 UTC. And the result of UnixNano()
does not depend on the location associated with t
.
We only have to set the seed once. But before we set the seed, rand
will use the default seed. For example, the first call of Intn(100)
always returns 81 without setting the seed.
1 | rand.Seed(time.Now().UnixNano()) |
Temporary variables in if-else block
In Go, we can define variables in if-else condition statement. The scope of these temporary variables is limited within if-else block.
1 | if v, p := math.Sqrt(float64(sum)), math.Sin(float64(sum)); v < 10 && p < 0.5 { |
switch
In Go, the execution does not fall through the switch
structure. Switch cases evaluate cases from top to bottom, stopping when a case succeeds. Also, if we want to take the same action on multiple cases, we can write case content1, content2:
.
In the following example, although without any break
, the control flow jumps out of the switch
block after executing the first case.
1 | var i int = 15640 |
1 | 15640 |
Slice
Slices share the same underlying memory with the original array.
- The type of slice is
[]T
. len()
returns the number of elements a slice contains.cap()
returns the number of elements in the underlying array, counting from the first element in the slice.append(slice, e1, e2, ...)
overwrites the elements in the underlying array ifslice
has enough capacity to contain these elements. Otherwise, it returns a new allocated slice and keeps the underlying array intact.
1 | func TestSlice() { |
1 | [0 1 2 3 4] |
Variable Length Array
Go only allows constants to be used as array size. To create an array whose size is defined by a variabl, we can use make([]T, length)
. It creates an underlying array with size of length
and returns a slice of it.
1 | const conLen = 8 |
String comparison
In Go, we can compare strings by using comparison operators (==
, <
, >
, etc) or by strings.Compare(s1, s2)
.
File naming convention
snake_case
is the convention across the most of the standard library and most third party libraries for Go.
defer
defer FunctionCall()
defers the execution of FunctionCall()
until the surrounding function (the parent function call of FunctionCall()
) returns. Multiple deferred calls are stacked, so that they will be callled in a LIFO order.
1 | func TestDefer() { |
1 | ready to return |
Hashmap
In Go, we can declaring a hashmap by using key word map
. map
declares a non-thread-safe hashmap.
- Create a hash map:
hashmap := make(map[string]int)
,string
is key type andint
is value type. - Insert a key-value pair:
hashmap[key] = value
. - Delete a key-value pair:
delete(hashmap, key)
. - Check whether a key exists:
v, ok := hashmap[key]
. If the key exists,ok == true
. Otherwise,v
equals to the zero value of the value type,ok == false
. - Traverse the hashmap:
for k, v := range hashmap {// do someting}
Difference between make and new
make
returns a value ofT
;new
returns a value of*T
.make
returns an initialized value;new
returns a zeroed value.make
can only create and initialize slices, maps and channels;new
can allocate zeroed memory for any types.
Take hashmap as an example. Since we need to initialize structure of hashmap itself before using it (it means that we cannot just zero the hashmap memory), we have to create a hashmap by hashmap := make(map[string]int)
or hashmap := map[string]int{}
.
Format go files recursively
1 | go fmt path/... |
Method
In Go, methods take either a value or a pointer as the receiver when they are called. Go interprets s.Print()
as (&s).Print()
, ps.SwapPrint()
as (*ps).SwapPrint()
. But this is just syntactic sugar.
Also, we cannot define new methods for existing types in another package (including built-in types, such as int
, map
). But it is fine to define a normal function that takes a non-local-package type argument. Or we can define our own alias for that type in current package type Alias ExistingTypeName
, or define a wrapper structure containing that type type NewStruct struct {n ExistingTypeName}
1 | type MyStruct struct { |
Interface
Any concrete types that implement all methods of a interface type satisfy implicitly that interface type. In other words, a interface type specifies a set of methods that a concrete type must possess to be considered an instance of that interface.
Note that T
and *T
are different when it comes to the relationship between interfaces and concrete types. And we cannot define methods for types under another package.
1 | type IntTask int |
Since only *ID
implements Run()
method, *ID
satisfies interface Runnable
but ID
does not.
1 | 1 |
Empty interface
interface{}
is empty interface. Because it has no methods, all types satisfy the empty interface.
Interface Pitfall
An interface value with a nil
dynamic value is NOT the same as a nil
interface for any non-empty interfaces.
If we assign nil
to an empty interface variable, that variable is still equal to nil
.
If you want to keep the interface value consistent with its concrete value, please do not define a concrete value first then assign it to the interface value, but define the interface value directly (allocate a concrete value by new
).
1 | func TestPitfall() { |
1 | run1 == nil ? false |
Sort
sort.Ints()
and sort.Strings()
only accept slice as argument. sort.Sort()
provide a customizable method that requires the data type to be sorted should implement Len()
, Swap()
and Less()
.
1 | var arr = [10]int{2, 3, 4, 6, 7, 9, 2, 3, 4, 5} |
Type Assertion
The general form of type assertion is x.(T)
, while x
is an interface to be tested (asserted), T
can be a concrete type or another interface type. There are two usages of type assertion.
T
is a concrete type. The type assertion checks whether the concrete type ofx
is identical toT
.T
is an interface type. The type assertion checks whether the concrete type ofx
satisfies interfaceT
.- A successful type assertion returns the dynamic value in type
T
, and an optional assertion flag. A failed type assertion returnsnil
and flagfalse
, if the statement only accepts one return value, it will cause a panic.
1 | func TestTypeAssert() { |
Type Switch
We can use a type switch statement to replace an if-else chain of type assertions.
1 | func TestTypeSwitch() { |
1 | case int: value = 15640, type = int |
map, channel and reference in Go
First of all, there is NO strict reference (alias) in Go, which means two variables refer (not point) to the same memeory location. In Go, everything is passed by value.
But we can see that in Go map
and channel
can be passed by reference in function. This is because map
and channel
are pointers naturally, while do not look like pointers. For example, a map
variable is actually a pointer to a runtime.hmap
structure.
By the way, channels act as FIFO queues.
Closed and drained channel
Read requests (not matter how many times) to a closed and drained channel always immediately returns a zero value. But we can examine receives on a closed and drained channel by using optional flag.
1 | func TestChannel() { |
Or we can read elements from a channel by range
, which will terminate the loop after reading the last element in a closed channel.
1 | func TestChannel() { |
Unidirectional channel types
chan<- T
: send-only channel; <-chan T
: receive-only channel. They can be used in function arguments to restrict the opertions to channels.
Conversions from bidirectional channel to unidirectional channel types are permitted in any assignment. But there is no going back.
nil channel
Send and receive operations on a nil channel block forever.
select
In select
, each case specifies a communication (a send or receive operation on some channel) and an associated block of statements.
- If there is only one runnable case, execute that case.
- If there is multiple runnable cases, randomly execute one of them.
- If there is no runnable cases, block.
The default
case in a select
is run if no other case is ready. We can use select
and a closed channel to terminate goroutines politely.
Terminate a goroutine
First, there is no way for one goroutine to terminate another directly. If we want to terminate a goroutine, we should send a “signal” to that goroutine and let it handles the signal by executing return
. This signal can be implemented by a closed and drained channel. Because each receive operations to a closed and drained channel always immediately returns a zero value.
It sounds great, right? But the problem is that if a goroutine is blocked, it cannot notice the signal at the same time. Thus, we need to ensure two things:
- If this routine may be blocked by other methods (
listener.Accept()
, etc), unblock these methods inClose()
, for example, bylistener.Close()
. - If this routine may be blocked by channel operations, always wrap every channel operation with a select-done structure. In other words, every channel operation should have a done case in parallel.
1 | var done = make(chan bool) // always empty |
If we use for range
to receive data from a channel in a goroutine function, which makes it hard to receive the termination signal, it can be rewritten by for{}
and select
.
1 | func sampleForRange() { |
An ingenious example of decoupling on channel operation
1 | // work routine pulls integers from input channel |
The constant generator iota
In a const declaration, the value of iota
begins at zero and increments by one for each item in the sequence.
1 | const ( |
Asynchronous Request Implementation
1 | type Request struct { |
Remove the first element of a slice
It also applies on slices with length of 1.
1 | slice = slice[1:] |
json.Marshal
json.Marshal(v)
takes a memory object (argument type interface{}
) and return its json encoding. In network programming, it can conveniently convert a user-defined object into json payload of the packet.
Flexible array members in a structure
Go allows us to define flexible array members (slices) in structures. Different from C, we can define any number of slices at any places in a structure.
1 | type Struct1 struct { |
Pop from a slice
s = s[1:]
. It removes the first element of s
. Note that when using this syntax, the length of s
can be 1 or greater but cannot be 0.
1 | func TestSlicePop() { |
Delete from a map
Deleting a non-existing key from a map through delete()
makes no effect.
1 | func TestMapDelete() { |
1 | [1, 1] |
Constants in Go
Go does NOT support constant members in structures or constant structures.
Implement timing events by ticker
We can create a ticker through time.NewTicker(interval)
, which generates ticks in its channel ticker.C
at specified time intervals (obviously, the size of channel C
is one. We do not want ticks to be accumulated). We can place the handling functions of timing events and other events in a for-select block, so that timing events will not affect the execution of normal events.
Note that Stop()
will stop sending ticks to the channel C
but will not close it, to prevent a closed drained channel issue.
1 | func TestTicker() { |
Implicit type conversions and implicit numeric conversions
Go does not allow implicit type conversions, and does not allow implicit numeric conversions except numeric conversions across constants.
1 | func TestType() { |
1 | i == k |
Go Programming Tips