1. Environment Setup

VSCode Download Link: https://code.visualstudio.com/

Solutions for Slow Downloads:

  • Find the required version on the official website, click download, and then copy the download link.
  • Replace the address in the red box with the domestic mirror address vscode.cdn.azure.cn.
  • Paste the link in the browser and download again.

Golang Download Link: https://golang.google.cn/dl/

  1. Install the Go plugin in VSCode.

  2. Update Go tools.

    Before updating, it’s important to note that due to domestic policies, direct updates are not possible, and a proxy is required.

    1# Old version, deprecated
    2go env -w GO111MODULE=on
    3go env -w GOPROXY=https://goproxy.io,direct
    
    1# New version, use the following link
    2go env -w GO111MODULE=on
    3go env -w GOPROXY=https://proxy.golang.com.cn,direct
    

    I encountered an error warning: go env -w GOPROXY=... does not override conflicting OS environment variable because there was already one set. After checking the environment variables, I found that they were set by the previous owner of the PC. I manually changed the environment variables, closed and reopened VSCode, and continued with the steps below.

    • In Visual Studio Code, open the Command Palette by going to Help > Show All Commands. Alternatively, use the keyboard shortcut (Ctrl+Shift+P).
    • Search for Go: Install/Update tools, then run the command from the tray.
    • When prompted, select all available Go tools and click “OK”.
    • Wait for the Go tools to finish updating.

2. Basic Syntax

2.1 time

2.1.1 time.sleep

The sleep function in golang time.sleep(1) and time.sleep(1 * time.Second).

The former only sleeps for 1 nanosecond, while the latter sleeps for 1 second.

2.1.2 Format

1time.Now().Format("2006-01-02 15:04:05") // Must use this specific time point to format

2.2 TrimRight(TrimLeft)

Function: strings.TrimRight(s string, cutset string)

Definition: Splits the string cutset into characters, then compares each character in the string from right to left until it encounters a character not present in cutset.

 1package main
 2
 3import (
 4	"fmt"
 5	"strings"
 6)
 7
 8func main() {
 9	fmt.Println("aabbccdd\t:", strings.TrimLeft("aabbccdd", "abcd"))  // Empty string
10	fmt.Println("aabbccdde\t:", strings.TrimLeft("aabbccdde", "abcd")) // e
11	fmt.Println("aabbeccdd\t:", strings.TrimLeft("aabbedcba", "abcd")) // edcba
12	fmt.Println("aabbccdd\t:", strings.TrimRight("aabbccdd", "abcd"))  // Empty string
13	fmt.Println("aabbccdde\t:", strings.TrimRight("aabbccdde", "abcd")) // aabbccdde
14	fmt.Println("aabbeccdd\t:", strings.TrimRight("aabbedcba", "abcd")) //aabbe
15}
16
17**********************************
18aabbccdd	: 
19aabbccdde	: e
20aabbeccdd	: edcba
21aabbccdd	: 
22aabbccdde	: aabbccdde
23aabbeccdd	: aabbe

2.3 iota

iota is a constant counter in Golang, which can only be used in constant expressions. It is reset to 0 when the const keyword appears, and increments by 1 for each new constant declaration in const (you can think of iota as the line index in the const block).

iota can only be used in constant expressions. It is reset to 0 when the const keyword appears, and different const blocks do not interfere with each other.

1const a = iota // a = 0 => iota = 0, a = iota
2const ( 
3  b = iota     // b = 0 => iota = 0, b = iota
4  c            // c = 1 => iota ++, c = iota
5  d			   // d = 2 => iota ++, d = iota
6)

2.4 fallthrough

In Go, switch is equivalent to having a break at the end of each case by default. After a successful match, it does not automatically execute the following case but exits the entire switch. However, you can use fallthrough to force the execution of the subsequent case code.

2.5 append

  • append() is used to add elements to the end of a slice and return the result.
  • The return value of append() must be received by the original slice variable.
  • When appending elements, if the slice still has capacity, the new elements will be placed in the remaining space behind the original slice. When the underlying array cannot accommodate more elements, Go will create a new underlying array to hold the slice, and the slice address will also change.
  • After allocating a new address, it will copy the elements from the original slice to the new slice one by one and return it.
 1package  main
 2import "fmt"
 3// Advanced slice operations
 4 
 5func main(){
 6	// append() adds elements to a slice
 7	s1 := []string {"火鸡面","辛拉面","汤达人"}
 8	fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n",s1,len(s1),cap(s1))
 9	
10	// The return value of append must be received by the original slice variable
11	s1 = append(s1,"小当家") // append dynamically adds elements. When the original underlying array cannot accommodate enough elements, the slice will start to expand, and Go will replace the underlying array.
12	fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n",s1,len(s1),cap(s1))
13
14	// Call append to add a slice
15	s2 := []string{"脆司令","圣斗士"}
16	s1 = append(s1,s2...) // ... means to unpack the slice and then add it
17	fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d",s1,len(s1),cap(s1))
18}
19
20/*
21	s1=[火鸡面 辛拉面 汤达人] len(s1)=3 cap(s1)=3
22	s1=[火鸡面 辛拉面 汤达人 小当家] len(s1)=4 cap(s1)=6
23	s1=[火鸡面 辛拉面 汤达人 小当家 脆司令 圣斗士] len(s1)=6 cap(s1)=6
24*/

2.6 string and byte conversion

1s1 := "hello"
2b := []byte(s1) // string to []byte
3s2 := string(b) // []byte to string

2.7 Interface usage

2.7.1 Anonymous fields and embedded structs

When we need to override some methods of a “struct that implements an interface” while keeping other methods unchanged, we need to use this approach.

 1package main
 2
 3import (
 4    "fmt"
 5)
 6
 7type Interface interface {
 8    Less(i, j int) bool
 9    Swap(i, j int)
10}
11
12// Array implements the Interface interface
13type Array []int
14
15func (arr Array) Less(i, j int) bool {
16    return arr[i] < arr[j]
17}
18
19func (arr Array) Swap(i, j int) {
20    arr[i], arr[j] = arr[j], arr[i]
21}
22
23// Anonymous interface
24type reverse struct {
25    Interface
26}
27
28// Override
29func (r reverse) Less(i, j int) bool {
30    return r.Interface.Less(j, i)
31}
32
33// Construct reverse Interface
34func Reverse(data Interface) Interface {
35    return &reverse{data}
36}
37
38func main() {
39    arr := Array{1, 2, 3}
40    rarr := Reverse(arr)
41    fmt.Println(arr.Less(0,1))
42    fmt.Println(rarr.Less(0,1))
43}

2.8 Importing structs from other files

Whether a struct’s properties are exported also follows the capitalization rule: those with uppercase first letters are exported, while those with lowercase first letters are not exported.

2.9 internal package

I didn’t pay attention to the special meaning of this package name before. I only knew it was internal, but I didn’t realize that Go directly sets packages with this name to have module-level private access. Previously, .pb.go files generated by proto were placed in the internal package in my project. I tried to import them in other projects but kept failing with the error use of internal package xxx not allowed.

After researching, I found that in Go 1.5 and later versions, you can create an internal package to make some program entities only accessible to other code within the current module. This is the third type of access: module-level private. Go packages in the internal directory of a Go project can only be imported by packages within the project. External projects cannot import packages from this internal directory.

 1.
 2├── module1
 3│   ├── go.mod
 4│   ├── internal
 5│   │   └── pkga
 6│   ├── pkg1
 7│   └── pkg2
 8└── module2
 9    ├── go.mod
10    └── pkg1

The internal/pkga package in module1 can be imported by pkg1 and pkg2 in module1. However, it cannot be imported by pkg1 in module2.

2.10 protobuf

1type NATSession struct {
2    Vrf   int32    `protobuf:"varint,1,opt,name=vrf,proto3" json:"vrf,omitempty"`
3}

In Golang, the protobuf tag after int32 in a struct is used to specify the encoding format of the struct field during serialization and deserialization. This is because the struct is used for Protocol Buffers (protobuf) serialization and deserialization operations.

2.11 omitempty

  1. When there is no json tag, the default field name is capitalized, and the default field name is used during serialization.
  2. When there is no json tag, the default field name is lowercase, and it will not be included in the serialization.
  3. When there is a json tag but no omitempty tag, the configured json name will be used during serialization (when the field is capitalized).
  4. When there is a json tag with an omitempty tag, fields with omitempty that are not assigned a value will be ignored during serialization. When a value is assigned, it will be displayed.
  5. When there is a json tag but the name is -, the field will be ignored during serialization if it is empty.

When a field in a struct has no value, json.Marshal() will not ignore these fields during serialization but will output the type’s zero value by default (e.g., int and float types default to 0, string type defaults to "", and object types default to nil). If you want to ignore fields without values during serialization, you can add the omitempty tag to the corresponding field.

 1type User struct {
 2	Name  string   `json:"name"`
 3	Email string   `json:"email"`
 4	Hobby []string `json:"hobby"`
 5}
 6
 7func omitemptyDemo() {
 8	u1 := User{
 9		Name: "左右逢源",
10	}
11	// struct -> json string
12	b, err := json.Marshal(u1)
13	if err != nil {
14		fmt.Printf("json.Marshal failed, err:%v\n", err)
15		return
16	}
17	fmt.Printf("str:%s\n", b)
18}
19
20/*
21	str:{"name":"左右逢源","email":"","hobby":null}
22*/
 1// Add omitempty in the tag to ignore empty values
 2// Note that hobby,omitempty together is the json tag value, separated by an English comma
 3type User struct {
 4	Name  string   `json:"name"`
 5	Email string   `json:"email,omitempty"`
 6	Hobby []string `json:"hobby,omitempty"`
 7}
 8
 9/*
10	str:{"name":"左右逢源"} // The serialized result does not include the email and hobby fields
11*/

2.12 Short variable declarations

2.12.1 Can only be used inside functions

1package main
2myvar := 1 //error
3func main() {  
4}

2.12.2 Redeclaring variables

You cannot redeclare a variable in a single declaration, but it is allowed in multi-variable declarations, where at least one new variable must be declared. Repeated variables need to be within the same code block; otherwise, you will get a hidden variable.

1package main
2func main() {  
3    one := 0
4    one := 1 //error
5}
1package main
2func main() {  
3    one := 0
4    one, two := 1,2
5    one,two = two,one
6}

2.13 Named return values

Go’s return values can be named, and they are treated as variables defined at the top of the function.

A return statement without arguments returns the named return values. This is known as a naked return.

 1package main
 2
 3import "fmt"
 4
 5func split(sum int) (x, y int) {
 6	x = sum / 3
 7	y = sum - x
 8	return
 9}
10
11func main() {
12	fmt.Println(split(9))
13}
14
15/*
16	3 6	
17*/

2.14 for loop

Go has only one loop structure: the for loop.

The basic for loop consists of three parts separated by semicolons:

  • Initialization statement: Executed before the first iteration.
  • Condition expression: Evaluated before each iteration.
  • Post statement: Executed at the end of each iteration.

The initialization and post statements are optional.

1func main() {
2	sum := 1
3	for ; sum < 1000; {
4		sum += sum
5	}
6	fmt.Println(sum) // 1024
7}

Removing the semicolons makes it similar to a while loop.

1func main() {
2	sum := 1
3	for sum < 1000 {
4		sum += sum
5	}
6	fmt.Println(sum) // 1024
7}

2.15 if statement

The if statement can execute a simple statement before the conditional expression, and the variable declared in this statement is only scoped within the if.

Variables declared in the short statement of if can also be used in any corresponding else blocks.

1func pow(x, n, lim float64) float64 {
2	if v := math.Pow(x, n); v < lim {
3		return v
4	} else {
5		fmt.Printf("%g >= %g\n", v, lim)
6	}
7	// v cannot be used here
8	return lim
9}

2.16 switch statement

A switch without a condition is the same as switch true, which is equivalent to an if-then-else statement.

2.17 defer statement

The defer statement postpones the execution of a function until the surrounding function returns.

Deferred function calls are pushed onto a stack. When the surrounding function returns, the deferred functions are called in last-in-first-out order.

 1func main() {
 2	fmt.Println("counting")
 3
 4	for i := 0; i < 3; i++ {
 5		defer fmt.Println(i)
 6		fmt.Println(i)
 7	}
 8
 9	fmt.Println("done")
10}
11/*
12	counting
13    0
14    1
15    2
16    done
17    2
18    1
19    0
20*/