CSE224 - Go Basis

How to create a go project

  1. mkdir myapp, cd myapp

  2. create a module: go mod init github.com/yourname/myapp, then go will generate a go.mod file inside.

  3. create main.go:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package main

    import (
    "fmt"
    )

    func main() {
    fmt.Println("Hello, Module!")
    }

    execute go run main.go to run it.

  4. add dependencies:

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"github.com/google/uuid"
)

func main() {
id := uuid.New()
fmt.Println("Generated UUID:", id)
}

after I execute go run main.go, go will download github.com/google/uuid and add require github.com/google/uuid v1.3.1 to go.mod config file.

Packages

1
2
3
4
5
6
7
8
myproject/

├── go.mod -> mod file
├── main.go -> package main
├── utils/
│ └── math.go -> package utils
├── service/
│ └── handler.go -> package service
1
2
3
4
5
6
7
8
// main.go
package main

import "myapp/utils"

func main() {
println(utils.SayHi())
}
1
2
3
4
5
6
// utils/helper.go
package utils

func SayHi() string {
return "Hi"
}
  1. Each .go file must have package xxx
  2. A module usually have many packages.
  3. Each folder must have one package.
  4. Can import between packages.
  5. Functions can freely call each other as long as they’re defined in the same package (even if they are in different files).
  6. In a Go project, the entry point is always the main package, specifically the main() function inside it. When I run go build, Go will look for package main and func main(), and then compiles all dependencies recursively together into a single executable.
  7. A module can have multiple main packages, each in a different subdirectory. Each main.go here is its own entry point, and Go builds them into separate executables.
1
2
3
4
5
6
7
8
9
mytool/              ← module
├── go.mod
├── cmd/
│ ├── server/
│ │ └── main.go ← package main (binary 1)
│ └── cli/
│ └── main.go ← package main (binary 2)
├── internal/
│ └── logic.go ← package internal (shared code)

Value Type

Assigning it to a new variable, or passing it to a function, creates a copy of the value.

  1. Basic types: int, float64, bool, string
  2. Arrays: [3]int, [5]byte
  3. Structs: type Point struct {x, y int}
  4. Custom types: type MyInt int
1
2
3
4
5
6
7
8
9
10
type Point struct {
X, Y int
}

p1 := Point{X: 1, Y: 2}
p2 := p1 // Copy of p1
p2.X = 100

fmt.Println(p1.X) // 1 — unchanged
fmt.Println(p2.X) // 100 — only p2 changed

Pointer Type

The following are pointer types:

  1. slice
    1
    2
    3
    4
    a := []int{1, 2, 3}
    b := a
    b[0] = 99
    fmt.Println(a[0]) // 99
  2. map
  3. channel
  4. function
  5. interface

Basic Syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
var x int = 42
z := 3.14

func add(a int, b int) int {
return a + b
}

type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name)

Array and Slices

Array:

Arrays are value types, so [3]int is not compatible with [4]int.

1
2
3
4
var arr [3]int // array of 3 integers, all initialized to 0
b := [3]int{1, 2, 3} // initialized with values
c := [...]int{4, 5, 6, 7} // size inferred to be 4
a[0] = 42 // mutation

Slices

A slice is a flexible, powerful view into an underlying array. It lets you work with sequences that can grow, shrink, or share memory

A slice is a struct with three fields:

  1. A pointer to the underlying array
  2. A length (number of visible elements)
  3. A capacity (maximum number of elements before reallocation) (cap(t) = number of elements from (start of the slice) to end of the backing array.)

Slice creation:

1
2
3
4
5
6
7
8
9
10
// 1. From an array
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // slice of [2, 3, 4]

// 2. Using a literal
s := []int{10, 20, 30}

// 3. Using make
s := make([]int, 3) // length 3, capacity 3
s := make([]int, 2, 5) // length 2, capacity 5

Slice internals:

1
2
s := []int{1, 2, 3, 4, 5}
t := s[1:4] // t = [2, 3, 4], length = 3, capacity = 4

Modification

1
2
3
4
5
6
7
8
s := []int{1, 2, 3, 4, 5}
t := s[1:4] // [2 3 4]
fmt.Println(len(t)) // 3
fmt.Println(cap(t)) // 4
// t and s points to the same array

t = append(t, 99) // still fits in capacity, modifies s[4]!
fmt.Println(s) // [1 2 3 4 99]

Append:
If there’s enough capacity, append extends the existing array. Otherwise, it allocates a new backing array.

1
2
s := []int{1, 2}
s = append(s, 3, 4) // s = [1, 2, 3, 4]

Pointers

1
2
3
4
5
6
7
8
9
10
11
12
13
var x int = 10
var px *int = &x // px holds the address of x
*px = 20 // sets x to 20 through the pointer

func modify(n *int) {
*n = 42
}

func main() {
a := 10
modify(&a) // pass address of a
fmt.Println(a) // 42
}

Loop

There is no while keyword in Go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for i := 0; i < 5; i++ {
fmt.Println(i)
}

i := 0
for i < 5 {
fmt.Println(i)
i++
}

// Infinite loop
for {
fmt.Println("This will run forever unless you break it")
}

// Looping over collections
// range returns two stuffs: index and value. index and value are declared and initialized automatically by the Go compiler as part of the loop syntax.
nums := []int{10, 20, 30}
for index, value := range nums {
fmt.Println("Index:", index, "Value:", value)
}

for _, value := range nums {
fmt.Println(Value:", value)
}

Map

Create a map:

1
2
3
4
5
6
7
8
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}

colors := make(map[string]string)
colors["red"] = "#FF0000"
colors["green"] = "#00FF00"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Iterate a map:
for name, age := range ages {
fmt.Printf("%s %d\n", name, age)
}


// Check if exists:
// ok is a boolean indicating whether the key exists in the map
age, ok := ages["Alice"]
if ok {
fmt.Println("Found:", age)
} else {
fmt.Println("Not found")
}

// delete a key
delete(ages, "Bob")

Function as parameters

1
2
3
func apply(fn func(int) int, val int) int {
return fn(val)
}

CSE224 - Go Basis
https://thiefcat.github.io/2025/04/03/CSE224/go-basis/
Author
小贼猫
Posted on
April 3, 2025
Licensed under