현재 3권의 신간들인 Go Optimizations 101, Go Details & Tips 101Go Generics 101이 출간되어 있습니다. Leanpub 서점에서 번들을 모두 구입하시는 방법이 비용 대비 효율이 가장 좋습니다.

Go에 대한 많은 정보들과 Go 101 책들의 최신 소식을 얻으시려면 Go 101 트위터 계정인 @Go100and1을 팔로잉 해주세요.

Go Tips 101

Index

How to force package users to use struct composite literals with field names?

Package developers can put a non-exported zero-size field in a struct type definition, so that compilers will forbid package users using composite literals with some field items but without field names to create values of the struct type.

An example:
// foo.go
package foo

type Config struct {
	_    [0]int
	Name string
	Size int
}
// main.go
package main

import "foo"

func main() {
	//_ = foo.Config{[0]int{}, "bar", 123} // error
	_ = foo.Config{Name: "bar", Size: 123} // compile ok
}

Please try not to place the zero-size non-exported field as the last field in the struct, for doing so might enlarge the size of the struct type.

How to make a struct type incomparable?

Sometimes, we want to avoid a custom struct type being used a map key types, then we can put a field of a non-exported zero-size incomparable type in a struct type to make the struct type incomparable. For example:
package main

type T struct {
	dummy        [0]func()
	AnotherField int
}

var x map[T]int // compile error: invalid map key type T

func main() {
	var a, b T
	_ = a == b // compile error: invalid operation:
}

Don't use value assignments with expressions interacting with each other.

Currently (Go 1.20), there are some evaluation orders in a multi-value assignment are unspecified when the expressions involved in the multi-value assignment interact with each other. So try to split a multi-value assignment into multiple single value assignments if there are, or you can't make sure whether or not there are, dependencies between the involved expressions.

In fact, in some bad-written single-value assignments, there are also expression evaluation order ambiguities. For example, the following program might print [7 0 9], [0 8 9], or [7 8 9], depending on compiler implementations.
package main

import "fmt"

var a = &[]int{1, 2, 3}
var i int
func f() int {
	i = 1
	a = &[]int{7, 8, 9}
	return 0
}

func main() {
	// The evaluation order of "a", "i"
	// and "f()" is unspecified.
	(*a)[i] = f()
	fmt.Println(*a)
}

In other words, a function call in a value assignment may the evaluation results of the non-function-call expressions in the same assignment. Please read evaluation orders in Go for details.

How to simulate for i in 0..N in some other languages?

We can range over an array with zero-size element or a nil array pointer to simulate such a loop. For example:
package main

import "fmt"

func main() {
	const N = 5

	for i := range [N]struct{}{} {
		fmt.Println(i)
	}
	for i := range [N][0]int{} {
		fmt.Println(i)
	}
	for i := range (*[N]int)(nil) {
		fmt.Println(i)
	}
}

We should reset the pointers in the element slots which are freed up in all kinds of slice manipulations to avoid memory leaking if we can't make sure if the freed-up element slots will be reused later.

Please read how to delete slice elements and kind-of memory leaking caused by not resetting pointers in dead slice elements for details.

Values of some types in standard packages are not expected to be copied.

Values of the bytes.Buffer type, strings.Builder type and the types in the sync standard package are not recommended to be copied. (They really should not be copied, though it is no problems to copy them under some specified circumstances.)

The implementation of strings.Builder will detect invalid strings.Builder value copies. Once such a copy is found, panic will occur. For example:
package main

import "strings"

func main() {
	var b strings.Builder
	b.WriteString("hello ")
	var b2 = b
	b2.WriteString("world!") // panic here
}

Copying values of the types in the sync standard package will be warned by the go vet command provided in Go Toolchain.
// demo.go
package demo

import "sync"

func f(m sync.Mutex) { // warning
	m.Lock()
	defer m.Unlock()
	// do something ...
}
$ go vet demo.go
./demo.go:5: f passes lock by value: sync.Mutex

Copying bytes.Buffer values will never be detected at run time nor by the go vet command. Just be careful not to do this.

We can use the memclr optimization to reset some contiguous elements in an array or slice.

Please read the memclr optimization for details.

How to check if a value has a method without importing the reflect package?

Use the way in the following example. (Assume the method needed to be checked is M(int) string.)
package main

import "fmt"

type A int
type B int
func (b B) M(x int) string {
	return fmt.Sprint(b, ": ", x)
}

func check(v interface{}) bool {
	_, has := v.(interface{M(int) string})
	return has
}

func main() {
	var a A = 123
	var b B = 789
	fmt.Println(check(a)) // false
	fmt.Println(check(b)) // true
}

How to efficiently and perfectly clone a slice?

Please read this wiki article and this wiki article for details.

We should use the three-index subslice form at some scenarios.

Assume a package provides a func NewX(...Option) *X function, and the implementation of this function will merge the input options with some internal default options, then the following implementation is not recommended.
func NewX(opts ...Option) *X {
	options := append(opts, defaultOpts...)
	// Use the merged options to build and return a X.
	// ...
}

The reason why the above implementation is not recommended is the append call may modify the underlying Option sequence of the argument opts. For most scenarios, it is not a problem. But for some special scenarios, it may cause some unexpected results.

To avoid modifying the underlying Option sequence of the input argument, we should use the following way instead.
func NewX(opts ...Option) *X {
	opts = append(opts[:len(opts):len(opts)], defaultOpts...)
	// Use the merged options to build and return a X.
	// ...
}

On the other hand, for the callers of the NewX function, it is not a good idea to think and rely on the NewX function will not modify the underlying elements of the passed slice arguments, so it is best to pass these arguments with the three-index subslice form.

Another scenario at which we should use three-index subslice form is mentioned in this wiki article.

One drawback of three-index subslice forms is they are some verbose. In fact, I ever made a proposal to make it less verbose, but it was declined.

Use anonymous functions to make some deferred function calls be executed earlier.

Please read this article for details.

Make sure and show a custom defined type implements a specified interface type.

We can assign a value of the custom defined type to a variable of type of the specified interface type to make sure the custom type implements the specified interface type, and more importantly, to show the custom type is intended to implement which interface types. Sometimes, writing docs in runnable code is much better than in comments.

package myreader

import "io"

type MyReader uint16

func NewMyReader() *MyReader {
	var mr MyReader
	return &mr
}

func (mr *MyReader) Read(data []byte) (int, error) {
	switch len(data) {
	default:
		*mr = MyReader(data[0]) << 8 | MyReader(data[1])
		return 2, nil
	case 2:
		*mr = MyReader(data[0]) << 8 | MyReader(data[1])
	case 1:
		*mr = MyReader(data[0])
	case 0:
	}
	return len(data), io.EOF
}

// Any of the following three lines ensures
// type *MyReader implements io.Reader.
var _ io.Reader = NewMyReader()
var _ io.Reader = (*MyReader)(nil)
func _() {_ = io.Reader(nil).(*MyReader)}

Some compile-time assertion tricks.

Besides the above one, there are more compile-time assertion tricks.

Several ways to guarantee a constant N is not smaller than another constant M at compile time:
// Any of the following lines can guarantee N >= M
func _(x []int) {_ = x[N-M]}
func _(){_ = []int{N-M: 0}}
func _([N-M]int){}
var _ [N-M]int
const _ uint = N-M
type _ [N-M]int

// If M and N are guaranteed to be positive integers.
var _ uint = N/M - 1
One more way which is stolen from @lukechampine. It makes use of the rule that duplicate constant keys can't appear in the same composite literal.
var _ = map[bool]struct{}{false: struct{}{}, N>=M: struct{}{}}
The above way looks some verbose but it is more general. It can be used to assert any conditions. It can be less verbose but needs a little more (negligible) memory:
var _ = map[bool]int{false: 0, N>=M: 1}

Similarly, ways to assert two integer constants are equal to each other:
var _ [N-M]int; var _ [M-N]int
type _ [N-M]int; type _ [M-N]int
const _, _ uint = N-M, M-N
func _([N-M]int, [M-N]int) {}

var _ = map[bool]int{false: 0, M==N: 1}

var _ = [1]int{M-N: 0} // the only valid index is 0
var _ = [1]int{}[M-N]  // the only valid index is 0

var _ [N-M]int = [M-N]int{}

The last line is also inspired by one of Luke Champine's tweets.

Ways of how to assert a constant string is not blank:
type _ [len(aStringConstant)-1]int
var _ = map[bool]int{false: 0, aStringConstant != "": 1}
var _ = aStringConstant[:1]
var _ = aStringConstant[0]
const _ = 1/len(aStringConstant)

The last line is stolen from Jan Mercl's clever idea.

Sometimes, to avoid package-level variables consuming too much memory, we can put assertion code in a function declared with the blank identifier. For example,
func _() {
	var _ = map[bool]int{false: 0, N>=M: 1}
	var _ [N-M]int
}

How to declare maximum int and uint constants?

const MaxUint = ^uint(0)
const MaxInt = int(^uint(0) >> 1)

How to detect native word size at compile time?

This tip is Go unrelated.
const Is64bitArch = ^uint(0) >> 63 == 1
const Is32bitArch = ^uint(0) >> 63 == 0
const WordBits = 32 << (^uint(0) >> 63) // 64 or 32

How to guarantee that the 64-bit value operated by a 64-bit atomic function call is always 64-bit aligned on 32-bit architectures?

Please read Go value memory layouts for details.

Avoid boxing large-size values into interface values.

When a non-interface value is assigned to an interface value, a copy of the non-interface value will be boxed into the interface value. The copy cost depends on the size of the non-interface value. The larger the size, the higher the copy cost. So please try to avoid boxing large-size values into interface values.

In the following example, the costs of the latter two print calls are much lower than the former two.
package main

import "fmt"

func main() {
	var a [1000]int

	// This cost of the two lines is high.
	fmt.Println(a)                   // a is copied
	fmt.Printf("Type of a: %T\n", a) // a is copied

	// The cost of the two lines is low.
	fmt.Printf("%v\n", a[:])
	fmt.Println("Type of a:", fmt.Sprintf("%T", &a)[1:])
}

About value sizes of different types, please read value copy costs in Go.

Optimize Go code by making use of BCE (bounds check elimination).

Please read this article to get what is BCE and how well BCE is supported by the standard Go compiler now.

Here, another example is provided:
package main

import (
	"strings"
	"testing"
)

func NumSameBytes_1(x, y string) int {
	if len(x) > len(y) {
		x, y = y, x
	}
	for i := 0; i < len(x); i++ {
		if x[i] != y[i] {
			return i
		}
	}
	return len(x)
}

func NumSameBytes_2(x, y string) int {
	if len(x) > len(y) {
		x, y = y, x
	}
	if len(x) <= len(y) { // more code but more efficient
		for i := 0; i < len(x); i++ {
			if x[i] != y[i] { // bound check eliminated
				return i
			}
		}
	}
	return len(x)
}

var x = strings.Repeat("hello", 100) + " world!"
var y = strings.Repeat("hello", 99) + " world!"

func BenchmarkNumSameBytes_1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = NumSameBytes_1(x, y)
	}
}

func BenchmarkNumSameBytes_2(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = NumSameBytes_2(x, y)
	}
}
In the above example, function NumSameBytes_2 is more efficient than function NumSameBytes_1. The benchmark result:
BenchmarkNumSameBytes_1-4   	10000000	       669 ns/op
BenchmarkNumSameBytes_2-4   	20000000	       450 ns/op

Please note, there are many small improvements in each main release of the standard Go compiler (gc). The trick used in the above example doesn't work for Go Toolchain versions earlier than 1.11. And future gc versions may become smarter so that the trick will become unnecessary. In fact, since gc v1.11, the bounds check for y[i] has been eliminated if x and y are slices, even if the above trick is not used.


Index↡

The Go 101 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.

주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.

이 책의 디지털 버전은 아래와 같은 곳을 통해서 구매할 수 있습니다.
Go 101의 저자인 Tapir는 2016년 7월부터 Go 101 시리즈 책들을 집필하고 go101.org 웹사이트를 유지 관리하고 있습니다. 새로운 콘텐츠는 책과 웹사이트에 수시로 추가될 예정입니다. Tapir는 인디 게임 개발자이기도 합니다. Tapir의 게임을 플레이하여 Go 101을 지원할 수도 있습니다. (안드로이드와 아이폰/아이패드용):
  • Color Infection (★★★★★), 140개 이상의 단계로 이루어진 물리 기반의 캐주얼 퍼즐 게임
  • Rectangle Pushers (★★★★★), 2가지 모드와 104개 이상의 단계로 이루어진 캐주얼 퍼즐 게임
  • Let's Play With Particles, 세가지 미니 게임이 있는 캐주얼 액션 게임
페이팔을 통한 개인 기부도 환영합니다.

색인: