These steps cover the Go you need to read most production Go code: packages and main, explicit error returns, slices and maps, structs with methods, and goroutines with channels.
1. Package, import, main
Every runnable Go program is package main with a main function. Unused imports and unused variables are compile errors on purpose — the language keeps files tidy for you.
package main
import "fmt"
func main() {
fmt.Println("Hello, Go")
}
2. Declare with := and know your zero values
Inside a function, := infers the type. Uninitialised values are never garbage: numbers are 0, strings are "", booleans are false, pointers and slices are nil.
package main
import "fmt"
func main() {
marks := 87
var name string
fmt.Printf("%q %d\n", name, marks)
}
3. Return errors, do not throw them
Go has no exceptions. A function that can fail returns (value, error) and the caller checks it immediately. This is the single most distinctive habit in Go.
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
v, err := divide(10, 0)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(v)
}
4. Slices, maps and structs with methods
append grows a slice, a map needs make before use, and methods attach to a type through a receiver rather than living inside a class body.
package main
import "fmt"
type Student struct {
Name string
Marks int
}
func (s Student) Passed() bool { return s.Marks >= 50 }
func main() {
list := []Student{{"Aarav", 82}, {"Diya", 41}}
for _, s := range list {
fmt.Println(s.Name, s.Passed())
}
}
5. Concurrency with goroutines and channels
go starts a lightweight thread; a channel passes values between goroutines and synchronises them. Receiving from a channel blocks until a value arrives.
package main
import "fmt"
func main() {
ch := make(chan string)
go func() { ch <- "done" }()
fmt.Println(<-ch)
}