Skip to content

Instantly share code, notes, and snippets.

@jpukg
Forked from baddri/go_note.md
Created February 14, 2022 20:59
Show Gist options
  • Save jpukg/144439ba8560bbaf6b6a3c9bc8556248 to your computer and use it in GitHub Desktop.
Save jpukg/144439ba8560bbaf6b6a3c9bc8556248 to your computer and use it in GitHub Desktop.
Go notes

Go note

my personal go note

Context

Context is context. you can pass time, value etc

Background

create empty root context

ctx := context.Background()

WithTimeout

create context that return timeout channel

ctx, cancel := context.WithTimeout(rootCtx, 2*time.Second)

// always defer cancel to relase memory
defer cancel()

func () {
	select {
	case <- time.After(3*time.Second):
		fmt.Println("done")
	case <- ctx.Done():
	log.Println(ctx.Error())
	}
}()

WithValue

create context with value

// create new key type to avoid collision
type myKey
const myid = mykey(10) // 10 is rand num

ctx := context.WithValue(ctx, myid, value)

// accesing value
value, ok := ctx.Value(myid).(string) // cast vlue to type

Other usage

ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
if err != nil {
	log.Fatal(err)
}

req = req.WithContext(ctx)
res, err := http.DefaultClient.Do(req)
if err != nil {
	log.Fatal(err)
}

Unit testing

func TestSum(t *testing.T) {
	tt := []struct {
		name  string
		value []float64
		res   float64
	}{
		{name: "basic sum", value: []float64{1, 2, 3, 4, 5}, res: 15},
		{name: "with negative", value: []float64{1, -5, 8, -10}, res: -6},
		{name: "with nil", value: nil, res: 0},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			res, err := sum.Sum(tc.value...)
			if err != nil {
				// fatal will stop execution
				t.Fatal("error while running Sum: ", err)
			}
			if res != tc.res {
				// error do not stop the execution
				t.Errorf("expected %v; got %v", tc.res, res)
			}
		})
	}
}

Example Test

example test create example and test. 2 bird with one stone

example_test.go

func ExampleSum() {
	sum, err := Sum(1, 2, 3, 4, 5)
	fmt.Println("sum of one to five is", sum)
	// Output: sum of one to five is 15

	// note: line 3 and 4 is neccessary. output must be exactly same as fmt.Println
}

Http Handler

func TestHttp() {
	srv := httptest.NewServer(router())
	defer srv.Close()

	req, err := http.NewRequest(http.MethodGet, srv.URL+"/?id=10", nil)
	if err != nil {
		t.Fatal("error while creating request: ", err)
	}

	rec := httptest.NewRecorder()
	handler(rec, req)

	res := rec.Result()

	if res.StatusCode != http.StatusOK {
		t.Errorf("expected Status OK; got %v", res.Status)
	}

	defer res.Body.Close()
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Fatalf("could not read body: %v", err)
	}
}

Go type

// Assume T is an arbitrary type and Tkey is
// a type supporting comparison (== and !=).

*T         // a pointer type
[5]T       // an array type
[]T        // a slice type
map[Tkey]T // a map type

// a struct type
struct {
	name string
	age  int
}

// a function type
func(int) (bool, string)

// an interface type
interface {
	Method0(string) int
	Method1() (int, bool)
}

// some channel types
chan T
chan<- T
<-chan T

examples

// The following new defined and source types
// are all basic types.
type (
	MyInt int
	Age   int
	Text  string
)

// The following new defined and source types are
// all composite types.
type IntPtr *int
type Book struct{author, title string; pages int}
type Convert func(in0 int, in1 bool)(out0 int, out1 string)
type StringArray [5]string
type StringSlice []string

func f() {
	// The names of the three defined types
	// can be only used within the function.
	type PersonAge map[string]int
	type MessageQueue chan string
	type Reader interface{Read([]byte) int}
}

Type aliasses

type MyType []int
type AnotherType MyType
// AnotherType has same memory structure with MyType but its different type.
// MyType != AnotherType

type MyType [2]int
type AnotherType = MyType
// now AnotherType is exactly the same as MyType. AnotherType is alias of MyType. it can even use MyType method

Execution tracer

Pprof

add this 2 line in the beginning function

pprof.StartCPUProfile(os.Stdout)
defer pprof.StopCPUProfile()

then ./program > cpu.pprof

to open: go tool pprof cpu.pprof

basic command

# list of most cpu usage
(pprof) top

# cumulative
(pprof) top -cum

# detail
(pprof) list main.main
# main.main is function name

# note: goo deeper using function name to get much more info

Tracer

add this 2 line in the beginning function

pprof.StartCPUProfile(os.Stdout)
defer pprof.StopCPUProfile()

then ./program > prog.trace

open trace: go tool trace prog.trace

Sync.Pool

# benchmem also benchmark memory
$ go test -bench=. -benchmem

create pool

myPool := sync.Pool{
	New: func () interface {} {
		return <your pool type>
	}
}

get pool

// doing this, you get ready to use object
data, ok := myPool.Get().(*<pointer your type cast>)

// return already used object to pool
defer myPool.Put(data)

// note: when you use data, make sure to initialise it to zero value first
// ex:
// data.Name = ""
// data.Age = 0
// data.Roles = nil

// the example below is wrong
// *data = Data{}
// dont do this, you just create one instance instead using available one

profiling

run go test -bench=. -benchmem -memprofile=mem.pb.gz

then pprof -http=[host]:[port] [options] source

you may need --alloc_objects as options

Stack and Heap

anything on the heap get managed by GC, so you may avoid much possible heap escape

GC is good, but cause latency. and in some program might cause some unpredictable behaviour.

when possible, the go compiler will allocate variable that are local to a function into thaf function stackframe

however if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable into garbage-collected heap to avoid dagling pointer errors

run go build -gcflags "-m" to see optimization decision

when are values constructed on the heap?

  1. when the value could possibly be referenced after the function that constructed the value returns
  2. when the compiler determine a value is too large to fit on the stack
  3. when the compiler doesn't know the size of the value at compile time (slice etc)

some commonly allocated values (go to heap)

  1. Values shared with ponters
  2. variable stored in interface variables
  3. func literal variable
  4. backing data for maps, channels, slices and string

which stays on the stack?

// definitely go to heap
func main() {
	b := read()
}

func read() []byte {
	b := make([], 32)
	return b
}
// b not escape to heap
func main() {
	b:= make([]byte, 32)
	read(b)
}

func read(b []byte) {
	// write into slive
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment