Go 1.13: Error Wrapping and Module Improvements
Go 1.13 is out. Here are the features I’m excited about.
1. Error Wrapping
Finally, standard error wrapping!
Before:
if err != nil {
return fmt.Errorf("failed to process: %v", err)
}
Problem: Lost the original error. Can’t use errors.Is() or errors.As().
After:
if err != nil {
return fmt.Errorf("failed to process: %w", err)
}
The %w verb wraps the error.
errors.Is()
Check if error is a specific type:
var ErrNotFound = errors.New("not found")
func GetUser(id string) (*User, error) {
user, err := db.Find(id)
if err != nil {
return nil, fmt.Errorf("get user: %w", ErrNotFound)
}
return user, nil
}
// Usage
user, err := GetUser("123")
if errors.Is(err, ErrNotFound) {
// Handle not found
}
Works even if error is wrapped multiple times!
errors.As()
Extract specific error type:
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
// Usage
var validationErr *ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Validation failed on field: %s\n", validationErr.Field)
}
errors.Unwrap()
Get the wrapped error:
err := fmt.Errorf("outer: %w", innerErr)
unwrapped := errors.Unwrap(err)
// unwrapped == innerErr
Real-World Example
package user
import (
"database/sql"
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("user not found")
ErrInvalid = errors.New("invalid user data")
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
func GetUser(id string) (*User, error) {
if id == "" {
return nil, &ValidationError{
Field: "id",
Message: "cannot be empty",
}
}
user, err := db.FindUser(id)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("get user %s: %w", id, ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// Usage
user, err := GetUser("")
if err != nil {
var validationErr *ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Validation error: %s\n", validationErr.Field)
return
}
if errors.Is(err, ErrNotFound) {
fmt.Println("User not found")
return
}
fmt.Printf("Unexpected error: %v\n", err)
}
2. Module Improvements
GOPRIVATE
For private repositories:
export GOPRIVATE=github.com/mycompany/*
No more proxy for private repos.
Module Mirror
Default proxy: proxy.golang.org
# Disable proxy
export GOPROXY=direct
# Use custom proxy
export GOPROXY=https://goproxy.io
go get Improvements
# Get latest version
go get github.com/pkg/errors
# Get specific version
go get github.com/pkg/errors@v0.9.1
# Get latest patch
go get github.com/pkg/errors@v0.9
# Upgrade all dependencies
go get -u ./...
# Upgrade only patch versions
go get -u=patch ./...
3. Number Literals
Binary Literals
b := 0b1010 // 10 in decimal
Octal Literals
// Old way
o := 0755
// New way (clearer)
o := 0o755
Hex Literals
h := 0xFF
Digit Separators
Make large numbers readable:
million := 1_000_000
billion := 1_000_000_000
hex := 0x_FF_FF_FF_FF
binary := 0b_1010_1010_1010_1010
4. TLS 1.3 by Default
TLS 1.3 is now the default for crypto/tls.
Faster handshakes, better security.
5. Improved defer Performance
defer is now faster (30% in some cases).
Our Migration
Upgraded all projects from Go 1.12 to 1.13.
Changes We Made
1. Updated Error Handling
// Before
if err != nil {
return fmt.Errorf("failed: %v", err)
}
// After
if err != nil {
return fmt.Errorf("failed: %w", err)
}
2. Added Error Checks
// Before
if err != nil {
if err.Error() == "not found" {
// Handle
}
}
// After
if errors.Is(err, ErrNotFound) {
// Handle
}
3. Used Digit Separators
// Before
timeout := 30000000000 // 30 seconds in nanoseconds
// After
timeout := 30_000_000_000 // Much clearer!
Should You Upgrade?
Yes:
- Error wrapping is a game-changer
- Module improvements are useful
- No breaking changes
We upgraded in one day. Smooth process.
The Verdict
Go 1.13 is a solid release. Error wrapping alone makes it worth upgrading.
If you’re on Go 1.11 or 1.12, upgrade now.
Questions? Let me know!