Environment Configuration
Initialize the Go workspace by setting environment variables to ensure proper module handling. Enable module mode globally and specify a reliable proxy for downloading external dependencies.
export GO111MODULE=on
export GOPROXY=https://goproxy.io,direct
export GOPATH=/path/to/gopath
For local private repositories, exclude them from public proxy checks using GOPRIVATE. Similarly, prevent checksum verification on private sums databases via GONOSUMDB to avoid failures or data leakage.
go env -w GOPRIVATE=*.internal.example.com,*.github-private.internal
go env -w GONOSUMDB=*.internal.example.com
In VS Code, install the official Go extension (Go) and run Go: Install/Update Tools from the command palette to fetch required auxiliary packages. Modify default behavior by editing settings.json within Preferences to customize linting paths or tool execution commands.
Dependency Management
Modules are initialized with go mod init <module_name>. Running go mod tidy resolves transitive dependencies, downloading verified code into GOMODCACHE. The resulting go.sum file acts as a cryptographic signature store ensuring package integrity.
Use go mod vendor if you need a self-contained project directory containing all source code, distinct from tidy which caches files externally.
package main
import (
"fmt"
"os"
)
func init() {
if err := os.Setenv("MY_ENV_VAR", "initialized"); err != nil {
log.Fatal(err)
}
}
func main() {
// Global state setup happens here before entry point
fmt.Println("Application started")
}
Initialization functions execute in declaration order during startup. They cannot be called manually from user code.
Language Fundamentals
Strings in Go are immutable byte slices (or runes for unicode). Accessing an index returns the byte value.
func analyzeString(s string) {
charMap := make(map[rune]int)
for _, char := range s {
charMap[char]++
}
fmt.Printf("Characters: %v\n", charMap)
}
Basic type defaults:
- Integers: 0
- Floats: 0.0
- Booleans: false
- Pointers/Channels/Slices: nil
- Strings: ""
- Maps: nil (must be initialized with
make)
Arrays have fixed sizes; slices are dynamic views over arrays. Slicing syntax arr[start:end] creates new slices referencing underlying memory.
Scope rules dictate that variables declared inside if, switch, or for blocks exist only within that block. To access counters outside loops, declare them prior to the loop statement.
Concurrency Patterns
Go relies on channels for communication between goroutines rather than shared memory.
Unbuffered Channels: Block both sender and receiver until the other side is ready.
func worker(ch chan int) {
val := <-ch
fmt.Println("Received:", val)
ch <- val * 2
}
func main() {
ch := make(chan int)
go worker(ch)
ch <- 5 // Blocks until worker receives
fmt.Println("Done")
}
Buffered Channels: Allow sending without immediate blocking up to a capacity limit.
func asyncWorker(bufSize int) {
ch := make(chan bool, bufSize)
go func() {
for i := 0; i < 3; i++ {
<-ch // Drain buffer
}
}()
ch <- true
ch <- true
close(ch)
}
The sync.WaitGroup synchronizes multiple goroutines. Add counts before starting workers, call Done() when finished, and Wait() in the main routine.
var wg sync.WaitGroup
tasks := []int{1, 2, 3}
wg.Add(len(tasks))
for _, t := range tasks {
go func(taskID int) {
defer wg.Done()
time.Sleep(time.Duration(taskID) * time.Second)
}(t)
}
wg.Wait()
Use select to manage multiple channel operations. It prevents blocking when combined with default. If no case is ready, default executes instantly.
for {
select {
case msg := <-channel:
process(msg)
default:
// Execute fallback logic periodically
time.Sleep(100 * time.Millisecond)
}
}
To signal termination across goroutines, close the channel. Reading from a closed channel yields the zero value immediately. A nil channel blocks forever.
Context Propagation
Context controls cancellation and timeouts across API boundaries. Create roots with Background() or TODO() when unsure.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
doWork(ctx)
Pass contexts through handlers. Use context.Value for request-scoped data like IDs or auth tokens. Define keys as unexported types to avoid collisions.
type key int
const RequestIDKey key = iota
ctx = context.WithValue(req.Context(), RequestIDKey, "uuid-123")
Standard Library Essentials
File operations utilize io/ioutil patterns migrated to io/fs and os. Use filepath.WalkDir for recursive traversal without following symlinks by default.
HTTP clients should reuse connections via http.Transport. Always close response bodies.
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 10,
},
Timeout: 10 * time.Second,
}
resp, err := client.Get(url)
if err == nil {
defer resp.Body.Close()
}
Signal handling allows graceful shutdown. Register for SIGINT and SIGTERM using os/signal.Notify. Listen on a channel to trigger cleanup routines before exiting.
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// ... long running task ...
<-sigChan
performShutdown()
Database interaction often uses ORMs like GORM. By convention, structs map to snake_case tables with pluralized names. Be cautious with nested transactions.
type User struct {
ID uint
Name string
}
func SaveUser(db *gorm.DB, u *User) error {
tx := db.Begin()
if err := tx.Create(u).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
Configuraton management can leverage INI parsers (ini.v1) or YAML libraries. Merge command-line flags (flag package) with config file values for flexibility.
Utility functions assist with time formatting. Use layout strings matching Mon Jan _2 15:04:05 2006 (Go reference time).
currentTime := time.Now().Format("2006-01-02T15:04:05")
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
String equality checks can use == "" or len(str) == 0. Both compile to identical assembly instructions.