Standard Library Pro Tips
Practical expertise for using Go's standard library correctly and efficiently.
Prefer Standard Library Firstβ
Before adding external packages, check whether the standard library can solve your problem. Go's standard library is powerful enough for most tasks, and minimizing dependencies improves build speed and makes security vulnerability management easier.
// bad β unnecessary external dependency
import "github.com/pkg/errors" // unnecessary for simple wrapping
// good β standard library is sufficient
import (
"errors"
"fmt"
)
func findUser(id int) error {
// errors.New β create new error, no extra context needed
if id == 0 {
return errors.New("ID cannot be zero")
}
// fmt.Errorf + %w β wrap error, add context
if id < 0 {
return fmt.Errorf("findUser: invalid ID %d: %w", id, ErrInvalidID)
}
return nil
}
var ErrInvalidID = errors.New("invalid ID")
fmt.Errorf vs errors.New β When to Use Whichβ
package main
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("resource not found")
ErrPermission = errors.New("permission denied")
)
// use errors.New β fixed message, package-level sentinel errors
var ErrTimeout = errors.New("timeout")
// use fmt.Errorf + %w β dynamic context + unwrappable error
func getResource(id int, userRole string) error {
if userRole != "admin" {
// wrapped with %w: errors.Is(err, ErrPermission) returns true
return fmt.Errorf("getResource(id=%d): %w", id, ErrPermission)
}
if id > 1000 {
return fmt.Errorf("getResource: ID %d %w", id, ErrNotFound)
}
return nil
}
// fmt.Errorf (without %w) β not wrapped, just a message
func validate(v string) error {
if v == "" {
return fmt.Errorf("value is empty: input=%q", v)
}
return nil
}
func main() {
err := getResource(42, "user")
fmt.Println(err) // getResource(id=42): permission denied
fmt.Println(errors.Is(err, ErrPermission)) // true β wrapped with %w
}
slog Level Configuration Per Environmentβ
package main
import (
"log/slog"
"os"
)
func initLogger(env string) *slog.Logger {
switch env {
case "production":
// production: JSON format, Info level and above only
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
// AddSource: true, // include source location if needed
}))
case "staging":
// staging: JSON format, including Debug
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
default: // "development"
// development: text format, with source location
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: true,
}))
}
}
func main() {
env := os.Getenv("APP_ENV")
if env == "" {
env = "development"
}
logger := initLogger(env)
slog.SetDefault(logger)
// outputs in format and level appropriate for the environment
slog.Debug("debug info", "env", env)
slog.Info("server started", "port", 8080)
slog.Warn("high memory usage", "used_mb", 450)
}
Reusing Compiled Regular Expressionsβ
package main
import (
"fmt"
"regexp"
)
// good β compile once at package level, reuse everywhere
var (
emailRe = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
phoneRe = regexp.MustCompile(`^(\+1|0)\d{9,10}$`)
slugRe = regexp.MustCompile(`[^a-z0-9]+`)
whitespace = regexp.MustCompile(`\s+`)
)
// bad β compiles on every call, poor performance
func validateEmailBad(email string) bool {
re := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
return re.MatchString(email) // recompiles every time
}
// good β uses package-level variable
func validateEmail(email string) bool {
return emailRe.MatchString(email)
}
func toSlug(s string) string {
// convert to lowercase, then replace non-alphanumeric with '-'
lower := whitespace.ReplaceAllString(s, "-")
return slugRe.ReplaceAllString(lower, "-")
}
func main() {
emails := []string{"user@example.com", "invalid", "test@test.co.uk"}
for _, e := range emails {
fmt.Printf("%s: %v\n", e, validateEmail(e))
}
fmt.Println(toSlug("Hello World! Go Language"))
}
time.Time Serialization Pitfalls β Timezone, UTC Conversionβ
package main
import (
"encoding/json"
"fmt"
"time"
)
type Event struct {
Name string `json:"name"`
// good β time.Time auto-serializes as RFC3339 (includes timezone)
StartTime time.Time `json:"start_time"`
}
func main() {
loc, _ := time.LoadLocation("America/New_York")
now := time.Now().In(loc)
event := Event{Name: "Seminar", StartTime: now}
data, _ := json.Marshal(event)
fmt.Println(string(data))
// {"name":"Seminar","start_time":"2024-01-15T10:30:00-05:00"}
// pitfall 1: parsing without timezone β parsed as UTC, not local
t1, _ := time.Parse("2006-01-02", "2024-01-15")
fmt.Println("parsed without timezone:", t1.Location()) // UTC
// correct: specify timezone explicitly
t2, _ := time.ParseInLocation("2006-01-02", "2024-01-15", loc)
fmt.Println("parsed with timezone:", t2.Location()) // America/New_York
// pitfall 2: storing local time in DB without UTC conversion
dbTime := now // storing local time as-is is risky
// correct: always convert to UTC before storing
dbTime = now.UTC()
fmt.Println("UTC for DB:", dbTime.Format(time.RFC3339))
// pitfall 3: ignoring timezone when comparing times
t3 := time.Date(2024, 1, 15, 10, 0, 0, 0, loc)
t4 := time.Date(2024, 1, 15, 15, 0, 0, 0, time.UTC) // same moment (UTC-5)
fmt.Println("Equal (same moment):", t3.Equal(t4)) // true β correct
fmt.Println("== (includes timezone):", t3 == t4) // false β wrong approach
// always use Equal() for time comparison
}
JSON Performance Optimizationβ
package main
import (
"bytes"
"encoding/json"
"fmt"
)
// tip 1: reuse buffers
var jsonBuf = &bytes.Buffer{}
func marshalReuse(v any) ([]byte, error) {
jsonBuf.Reset()
enc := json.NewEncoder(jsonBuf)
if err := enc.Encode(v); err != nil {
return nil, err
}
// remove trailing newline
b := jsonBuf.Bytes()
if len(b) > 0 && b[len(b)-1] == '\n' {
b = b[:len(b)-1]
}
result := make([]byte, len(b))
copy(result, b)
return result, nil
}
// tip 2: use json.Number to preserve large integers / precision
func parseWithPrecision() {
jsonStr := `{"id": 9007199254740993, "price": 1234567890.123456789}`
// bad β default parsing loses float64 precision
var v1 map[string]interface{}
json.Unmarshal([]byte(jsonStr), &v1)
fmt.Printf("float64 id: %v\n", v1["id"]) // precision may be lost
// good β json.Number preserves original string
decoder := json.NewDecoder(bytes.NewReader([]byte(jsonStr)))
decoder.UseNumber()
var v2 map[string]interface{}
decoder.Decode(&v2)
id := v2["id"].(json.Number)
fmt.Printf("Number id: %s\n", id.String()) // exact value
// tip 3: parse known structures into structs, not maps (2-3x faster)
}
func main() {
data := map[string]string{"hello": "world"}
b, err := marshalReuse(data)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(string(b))
parseWithPrecision()
}
reflect Package Cautionsβ
package main
import (
"fmt"
"reflect"
)
type Config struct {
Host string
Port int
TLS bool
}
// reflect has performance cost β avoid in hot paths
func printFields(v any) {
rv := reflect.ValueOf(v)
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
value := rv.Field(i)
fmt.Printf("%s: %v (type: %s)\n", field.Name, value.Interface(), field.Type)
}
}
// alternative: use interfaces or explicit functions
func printConfig(c Config) {
fmt.Printf("Host: %s, Port: %d, TLS: %v\n", c.Host, c.Port, c.TLS)
}
// reflect is appropriate for: general-purpose libraries, ORMs, serialization frameworks
func deepEqual(a, b any) bool {
return reflect.DeepEqual(a, b)
}
func main() {
cfg := Config{Host: "localhost", Port: 8080, TLS: true}
fmt.Println("=== using reflect ===")
printFields(cfg) // slow but generic
fmt.Println("=== direct access ===")
printConfig(cfg) // fast and clear
// DeepEqual β useful for comparing nested structures
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
s3 := []int{1, 2, 4}
fmt.Println("s1 == s2:", deepEqual(s1, s2)) // true
fmt.Println("s1 == s3:", deepEqual(s1, s3)) // false
// reflect pitfalls:
// 1. reflect.Value.Interface() can panic on unexported fields
// 2. generics (Go 1.18+) replace many reflect use cases
// 3. no compile-time type checking β runtime error risk
}
Key Summaryβ
| Tip | Recommendation |
|---|---|
| Error creation | fixed message β errors.New, add context β fmt.Errorf + %w |
| Logging | production β slog JSON, development β slog text |
| Regular expressions | reuse with package-level MustCompile variable |
| Time storage | call .UTC() before storing, use .Equal() for comparison |
| JSON parsing | known structure β struct, need precision β UseNumber() |
| reflect | only in general-purpose libraries, use interfaces or generics in hot paths |