Go Language Fundamentals: Core Concepts and Syntax
Language Structure
package main
import (
"fmt"
)
func main() {
fmt.Print("Hello World!")
}
Execuiton:
go run main.go
go build main.go
./main
Go Modules
go env
go env -w GO111MODULE=on
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,https://goproxy.cn,direct
Environment Variables:
GOSUMDB- validates dependency integrityGONOSUMDB- excludes specific domains from checksum verificationGONOPROXY- bypasses proxy for specific modulesGOPRIVATE- marks modules as private, bypassing proxy
Workflow:
- Enable
GO111MODULEand configure proxy - Create project directory:
mkdir project/ - Create module directory:
mkdir module/ - Initialize:
go mod init module_name
Common Commands:
go mod init # Initialize new module
go mod tidy # Update dependencies
go mod download # Download dependencies
go mod vendor # Copy dependencies to vendor/
go mod verify # Verify dependencies
go mod edit # Manually edit go.mod
Package Imports
import "example/package"
Initialization Order:
- Package
exampleloads → importsexample2 example2loads → no further importsexample2executes global variables, constants, andinit()exampleexecutes its global declarations- Execution propagates upward
init() runs before main(), making it suitable for database connections and environment setup.
Blank Identifier:
import (
_ "lib1"
)
This calls lib1's init() without causing unused import errors.
Variables
var a int = 10
var b = 10
c := 10
var d, e, f = 1, 2, 3
Notes:
:=cannot reuse existing variables- Variable declarations are mandatory
Constants
const a int = 10
iota Usage:
const (
a = iota // 0
b // 1
c // 2
d = "ha" // 3
e // "ha"
f = 100 // 100
g // 100
h = iota // 7
i // 8
)
iota is a special constant that resets to 0 in each const block and increments automatically.
Conditional Statements
If Statement
var a int
fmt.Scanf("%d", &a)
if a > 10 {
fmt.Println("a > 10")
} else if a <= 10 {
fmt.Println("a <= 10")
}
Switch Statement
switch i := b.(type) {
case nil:
fmt.Println("b is nil")
case int:
fmt.Println("b is int")
default:
fmt.Println("b is string")
}
Fallthrough
switch i := 10 {
case 5:
fmt.Println("i is 5")
case 10:
fmt.Println("i is 10")
fallthrough
default:
fmt.Println("i is 15")
}
// Output: i is 10, i is 15
fallthrough forces execution of the next case, ignoring case matching.
Select Statement
Used exclusively with channels. Each case must be a channel operation (send or receive).
package main
import "fmt"
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
for {
ch1 <- "ch1"
}
}()
go func() {
for {
ch2 <- "ch2"
}
}()
for {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
fmt.Println("no message received")
}
}
}
If no channel is ready and no default case exists, the select blocks.
Loops
// Range iteration
for key, value := range smap {
// key and value
}
// Traditional for loop
for i := 10; i > 0; i-- {
// loop body
}
// While-style loop
i := 20
for i > 10 {
i--
}
// Infinite loop
for {
// loop body
}
Break with Label:
re:
for i := 10; i > 8; i-- {
break re
}
Functions
func functionName() (returnType1, returnType2) {
// function body
return
}
func max(num1, num2 int) int {
if num1 > num2 {
return num1
}
return num2
}
Note: All function parameters in Go are passed by value.
Arrays
Fixed-length, strongly-typed sequences:
var arr [3]int = [3]int{1, 2, 3}
arr := [3]int{1, 2, 3}
r := [...]int{99: -1} // 100 elements, index 99 = -1
a := [...]int{1, 2, 3, 4, 5} // length inferred as 5
Important: [3]int and [4]int are different types.
Slices
Dynamic-length arrays backed by an underlying array. A slice consists of: pointer to first element, length, and capacity.
var slice1 []int = make([]int, length, capacity)
slice1 := make([]int, length, capacity)
s := []int{1, 2, 3}
append() Behavior: When capacity is exceeded, Go allocates a new array (2x capacity) and copies existing elements. Modifications to the new slice won't affect the original array.
Maps
Unordered key-value pairs:
// Declaration
var myMap1 map[string]int // nil map
myMap1 = make(map[string]int, 10)
myMap1 := make(map[string]int)
myMap1 := map[string]int{
"one": 1,
"two": 2,
}
// Operations
myMap2["china"] = "Beijing" // add/update
delete(myMap2, "china") // delete
// Iteration
for key, value := range myMap2 {
fmt.Println(key, value)
}
Maps are reference types when passed as parameters.
Structs
type MyInt int
type Book struct {
name string
price float64
}
func (b Book) GetName() string {
return b.name
}
Method Receivers: Value vs Pointer
// Value receiver - receives a copy
func (b Book) SetName(newName string) {
b.name = newName // doesn't affect original
}
// Pointer receiver - receives a reference
func (b *Book) SetName(newName string) {
b.name = newName // modifies original
}
Composition (Not Inheritance)
type Person struct {
name string
age int
}
type SuperHero struct {
Person
canFly bool
}
// Initialization
hero := SuperHero{Person{"John", 30}, true}
// or
var hero SuperHero
hero.name = "John"
hero.age = 30
hero.canFly = true
Go uses composition instead of inheritance.
Struct Tags
type Movie struct {
Title string `json:"title"`
Price string `json:"price"`
}
// Struct to JSON
jsonStr, err := json.Marshal(movie)
// JSON to Struct
var movie Movie
err = json.Unmarshal(jsonStr, &movie)
type Movie struct {
Title string `info:"title" doc:"movie title"`
}
func getTagContent(arg interface{}) {
t := reflect.TypeOf(arg).Elem()
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i).Tag.Get("info"))
}
}
Interfaces and Polymorphism
type Action interface {
Sleep()
GetColor() string
SetColor(color string)
}
type Animal struct {
name string
color string
}
func (a *Animal) Sleep() {}
func (a *Animal) GetColor() string { return a.color }
func (a *Animal) SetColor(color string) { a.color = color }
var cat Action = &Animal{name: "cat"}
cat.SetColor("black")
A type implements an interface by providing all method definitions.
Empty Interface
func process(arg interface{}) {
// Type assertion
value, ok := arg.(string)
if ok {
fmt.Println("string value:", value)
}
// Type switch
switch v := arg.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
}
}
Every type implements the empty interface interface{}. Internally, a variable consists of a type and value (pair).
defer
Executes after the surrounding function returns, but before the return statement completes. Multiple defers execute in LIFO order.
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
// Output: hello, world
Recover
func recoverFromPanic() {
if err := recover(); err != nil {
fmt.Println("Recovered:", err)
}
}
func main() {
defer recoverFromPanic()
panic("something went wrong")
}
return executes before defer within the same function.
Reflection
reflect.TypeOf(value) // returns reflect.Type
reflect.ValueOf(value) // returns reflect.Value
// Using %T in fmt prints the type
fmt.Printf("%T\n", variable)
Goroutines
Lightweight threads managed by the Go runtime. The main goroutine must not finish before others complete.
func worker() {
for i := 1; ; i++ {
fmt.Println("worker:", i)
time.Sleep(1 * time.Second)
}
}
func main() {
go worker()
for i := 1; ; i++ {
fmt.Println("main:", i)
time.Sleep(1 * time.Second)
}
}
Without a wait mechanism, main may exit before goroutines complete.
WaitGroup
var wg sync.WaitGroup
func task(id int) {
defer wg.Done()
fmt.Printf("Task %d completed\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
wg.Add(1)
go task(i)
}
wg.Wait()
}
Channels
Communication mechanism between goroutines:
ch := make(chan int)
ch <- 10 // send
value := <-ch // receive
Unbuffered Channels
Both send and receive block until the other party is ready.
Buffered Channels
ch := make(chan int, 3)
fmt.Println(len(ch), cap(ch))
Closing Channels
func produce(ch chan int) {
for i := 0; i <= 5; i++ {
ch <- i
if i == 5 {
close(ch)
return
}
}
}
func main() {
ch := make(chan int)
go produce(ch)
for {
if value, ok := <-ch; ok {
fmt.Println("Received:", value)
} else {
break
}
}
fmt.Println("Channel closed")
}
Only close channels when no further sends are expected.
Channel with Range and Select
// Using range to receive from channel
for data := range ch {
fmt.Println(data)
}
// Using select for multiple channels
for {
select {
case <-ch1:
fmt.Println("ch1 readable")
case ch2 <- data:
fmt.Println("ch2 writable")
}
}
range automatically stops when the channel is closed.