[Golang] Internals and tricks (1)

Brian Pan
5 min readAug 23, 2023

String

type String struct {
str unsafe.Pointer
len int
}

// use operator += is the best choice if it is known as string type
func concatStringByBytesBuffer(s1 []string) string {
var b bytes.Buffer
for _, v := range s1 {
b.WriteString(v)
}

b.String()
}

// transition between []byte, []rune
s := "中國菜"
rs := []rune(s)
s1 := []byte(s)

for i, v := range rs {
var utf8Bytes []byte
// each chinese character has 3 bytes
for j := i; i < (i+1)*3 ;j++ {
utf8Bytes = append(utf8Bytes, s1[j])
}
fmt.Printf("%s => %X => %X \n", string(v), v, utf8Bytes)
}

// range []byte(string) has compiler optimization
func convert() {
s := "Heoo"
s1 := []byte(s)

for _, v := range s1 {
_ = v
}
}

func convertOpt() {
s := "Heoo"
for _, v := range []byte(s) {
_ = v
}
}

testing.AllocsPerRun(1, convert) // 1
testing.AllocsPerRun(1, convertOpt) // 0

Slice

type Slice struct {
array unsafe.Pointer
len int
cap int
}
- slice will copy to another place when reaching cap limit
u := []int{11,12,13,14,15}
s := u[1:3]
s = append(s, 24)
s = append(s, 25)
s = append(s, 26)

For range traps

// (5 6) *6
// this is because the closure only captures the final value of the range
func trap1() {
var m [...]int{1, 2, 3, 4, 5, 6}
for i,v := range m {
go func() {
time.Sleep(time.Second * 2)
fmt.Println(i, v)
}
}
}

func correct1() {
var m [...]int{1, 2, 3, 4, 5, 6}
for i,v := range m {
go func(i, v int) {
time.Sleep(time.Second * 2)
fmt.Println(i, v)
}(i, v)
}
}

// iterator is the copy of the data
func trap2() {
var a = [4]int{1,2,3,4}
var r [4]int
// a is actual a copy ('a) use &a to get the reference
for i, v := range a {
if i < 2 {
a[i] = v + 10
}
r[i] = v
}
}

fmt.Println("r = ", r)
fmt.Println("a = ", a)

// for range string, map channel will create the copy
// lucy may or may not include in the for range
func trap3(){
var m = map[string]int {
"tony": 1,
"jack": 2,
"mary": 3,
}
counter := 0
for k,v := range m {
if counter == 0 {
m["xxx"] = 4
}
counter++
fmt.Println("%s:%d", k, v)
}
}

// break by label
func trap4() {
exit := make(chan interface{})

go func() {
loop:
for {
select {
case <- time.After(time.Second):
fmt.Println("tick")
case <- exit:
break loop
}
}
fmt.Println("exit")
}()

time.Sleep(3*time.Second)
exit <- struct{}{}
time.Sleep(3*time.Second)
}

Map

- hashcode = Hash(key)
- hashcode splits into 2 parts
highend, lowend
lowend for choosing bucket
highend for retrieving tophash index which is a cache to search key entry
type maptype struct {
typ _type
key *_type
elem *_type
bucket *_type
keysize unit8
elemsize uint8
bucketsize uint8 // bucket size default is 8
flags uint32
}
- map extends is based on load factor -> default is 6.5 (src/runtime/map.go)Takeaway
- predefine cap value to avoid unnecessary move operations

Functions

// use _ for init()
import (
_ "github.com/lib/pq"
)
// github.com/lib/pq/conn.go
func init() {
sql.Register("postgres", &Driver{})
}
// currying
func partialMul(x int) func(int) int {
return func(y int) {
x * y
}
}
// functor
// use a wrapper struct with implemented interface
type IntSliceFunctor interface {
Fmap(fn func(int) int) IntSliceFunctor
}
type intSliceFunctorImpl struct {
ints []int
}
func (isf intSliceFunctorImpl) Fmap(fn func(int) int) IntSliceFunctor {
newInts := make([]int, len(isf.ints))

for i, elt := range isf.ints {
retInt := fn(elt)
newInts[i] = retInt
}
// return a new impl functor
return intSliceFunctorImpl{ints: newInts}
}
//long params
func append(slice []Type, elem ...Types) []Type
func Println(a ...interface{}) (n int, err error)
// optional modetype House {
style int
price int
}
type Option func(*House)
func NewHouse(options ...Option) *House {
&h = House {
style: 1,
price: 1000,
}
for _, option := range options {
option(h)
}
return h
}
func WithPrice(price p) Option {
return func(h *House) {
h.price = p
}
}
func main() {
h := NewHouse(WithPrice(1010))
}
Dump function
func DumpMethodSet(i interface{}) {
v := reflect.TypeOf(i)
elemTyp := v.Elem()
n := elemTyp.NumMethod()
if n == 0 {
fmt.Printf("%s's method set is empty!\n", ele)
return
}
fmt.Printf("%s's methind set: \n", elemTyp)
for j := 0 j < n;j++ {
fmt.Println("-", elemTyp.Method(j).Name)
}

fmt.Printf("\n")
}

Errors

  • errors.Is(err2, ErrSentinel) -> check if err2 is ErrSentinel
var ErrSentinel = errors.New("the underlying Sentinel error")
err2 := fmt.Errorf("wrap err2: %w", ErrSentinel)
errors.Is(errs, ErrSentinel)
  • errors.As(err, &e) -> if e, ok := err.(*MyError);ok{//…}
// error interface
type interface error {
Error() string
}
func Marshal(v interface{}) ([]byte, error) // encoding
func Unmarshal(data []byte, v interface{}) error // decoding
// Error handling patterns
// Scout pattern
// bufio case
var (
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
ErrInvalidUnreadRune = errors.New("bufio: invaild use of UnreadRune")
)
data, err := b.Peek(1)
if err != nil {
switch err {
case bufio.ErrInvalidUnreadByte:
// ...
return
}
}
// json decoder example
type UnmarshalTypeError struct {
Value string
Type reflect.Type
Offset int64
Struct string
Field string
}
func (d *decodeState) addErrorContext(err error) error {
if d.errorContext.Struct != nil || len(d.errorContext.FieldStack) > 0 {
switch err := err.(type) {
case *UnmarshalTypeError:
err.Struct = d.errorContext.Struct.Name()
err.Field = strings.Join(d.errorContext.FieldStack, ".")
return err
}
return err
}
  • checked handle style

cons: performance degrade because of panic, recover

func check(err error) {
if err != nil {
panic(err)
}
}
func CopyFile(src,dst string) (err error) {
var r, w *os.File
defer func() {
if r != nil {
r.Close()
}
if w != nil {
w.Close()
}
if e := recover(); e != nil {
if w != nil {
os.Remove(dst)
}
}
}
r, err = os.Open(src)
check(err)
w, err = os.Create(dst)
check(err)
_, err = os.Copy(w, r)
check(err)
return
}
  • embedded error optimization
//$GOROOT/src/bufio/bufio.go
type Writer struct {
err error
buf []byte
n int
wr io.Writer
}
func (b *Writer) Write(p []byte) (nn int, err error) {
for len(p) > b.Available() && b.err == nil {
// ...
}
if b.err != nil {
return nn, b.err
}
//...
return nn, nil
}
// example in the book
type FileCopier struct {
w *os.File
r *os.File
err error
}
func(f *FileCopier) open(path string) (*os.File, error) {
if f.err != nil {
return nil, f.err
}

h, err := os.Open(path)
if err != nil {
f.err = err
return nil, err
}
return h, nil
}
func(f *FileCopier) openSrc(path string) {
if f.err != nil {
return
}
f.r, f.err = f.open(path)
return
}
func(f *FileCopier) createDst(path string) {
if f.err != nil {
return
}
f.w, f.err = os.Create(path)
return
}
func(f *FileCopier) copy() {
if f.err != nil {
return
}
if _, err := os.Copy(f.w, f.r); err != nil {
f.err = err
}
return
}
func(f *FileCopier) copyFile(src, dst string) error {
if f.err != nil {
return
}
defer func() {
if f.r != nil {
f.r.Close()
}
if f.w != nil {
f.w.Close()
}
if f.err != nil {
if f.w != nil {
os.Remove(dst)
}
}
}
// list of operations
f.openSrc(src)
f.createDst(dst)
f.copy()

return f.err
}

JSON

  • use gjson to fast process json string
func (a *amqpMonitor) parseAPIStat(json string) (stat amqpAPIStat, err error) {
stat.vhosts = make([]amqpVhostStat, 0)

vhostArray := gjson.Parse(json).Array()
for _, vhostObj := range vhostArray {

vhost := amqpVhostStat{}
vhost.Name = vhostObj.Get("name").String()
messageStats := vhostObj.Get("message_stats")
vhost.AckCount = int(messageStats.Get("ack").Int())
vhost.ConfirmCount = int(messageStats.Get("confirm").Int())
vhost.DeliverCount = int(messageStats.Get("deliver").Int())
vhost.ReadyCount = int(vhostObj.Get("messages_ready").Int())
stat.vhosts = append(stat.vhosts, vhost)
}

return
}

--

--