A modern, generics-based error handling library for Go that brings throw-catch semantics to idiomatic Go code.
Go's explicit error handling is powerful but can lead to verbose code. rg
provides a clean, panic-based approach that:
- β
Reduces boilerplate: Eliminate repetitive
if err != nil
checks - β Maintains safety: Automatically converts panics back to errors
- β Type-safe: Full generics support for any return type combination
- β Context-aware: Built-in support for Go contexts
- β Hook-friendly: Customizable error handling with callbacks
- β Zero dependencies: Pure Go standard library
go get github.com/yankeguo/rg
Transform verbose error handling:
// Before: Traditional Go error handling
func processFile(filename string) ([]byte, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
processed, err := processData(data)
if err != nil {
return nil, err
}
return processed, nil
}
// After: With rg
func processFile(filename string) (result []byte, err error) {
defer rg.Guard(&err)
data := rg.Must(os.ReadFile(filename))
result = rg.Must(processData(data))
return
}
rg.Guard(&err)
acts as a safety net that catches any panic and converts it to an error:
func riskyOperation() (err error) {
defer rg.Guard(&err)
// Any panic here will be caught and converted to err
rg.Must0(someFunctionThatMightFail())
return nil // Success case
}
The Must
family of functions check for errors and panic if found:
rg.Must0(err)
- For functions returning only an errorrg.Must(value, err)
- For functions returning one value + errorrg.Must2(v1, v2, err)
- For functions returning two values + error- ... up to
rg.Must7
for seven values + error
Pass context information through the error handling chain:
func processWithContext(ctx context.Context) (err error) {
defer rg.Guard(&err, rg.WithContext(ctx))
// Context is available in error callbacks
result := rg.Must(someNetworkCall(ctx))
return nil
}
Customize error handling with global hooks:
func init() {
// Global error hook (deprecated, use OnGuardWithContext)
rg.OnGuard = func(r any) {
log.Printf("Error caught: %v", r)
}
// Context-aware error hook
rg.OnGuardWithContext = func(ctx context.Context, r any) {
// Extract request ID, user info, etc. from context
if reqID := ctx.Value("request_id"); reqID != nil {
log.Printf("Error in request %v: %v", reqID, r)
}
}
}
func convertJSONToYAML(inputFile string) (err error) {
defer rg.Guard(&err)
// Read and parse JSON
jsonData := rg.Must(os.ReadFile(inputFile))
var data map[string]interface{}
rg.Must0(json.Unmarshal(jsonData, &data))
// Convert to YAML and write
yamlData := rg.Must(yaml.Marshal(data))
rg.Must0(os.WriteFile(inputFile+".yaml", yamlData, 0644))
return nil
}
func handleUserCreation(w http.ResponseWriter, r *http.Request) {
var err error
defer rg.Guard(&err, rg.WithContext(r.Context()))
defer func() {
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}()
// Parse request
var user User
rg.Must0(json.NewDecoder(r.Body).Decode(&user))
// Validate and save
rg.Must0(user.Validate())
savedUser := rg.Must(userService.Create(user))
// Return response
w.Header().Set("Content-Type", "application/json")
rg.Must0(json.NewEncoder(w).Encode(savedUser))
}
func transferMoney(from, to int64, amount decimal.Decimal) (err error) {
defer rg.Guard(&err)
tx := rg.Must(db.Begin())
defer tx.Rollback() // Safe to call even after commit
// Perform transfer operations
rg.Must0(debitAccount(tx, from, amount))
rg.Must0(creditAccount(tx, to, amount))
rg.Must0(logTransfer(tx, from, to, amount))
rg.Must0(tx.Commit())
return nil
}
- Always use
defer rg.Guard(&err)
at the beginning of functions that need error handling - Keep guard simple: Don't put complex logic in the defer statement
- Use context: Pass context for better error tracking and debugging
- Combine with traditional error handling:
rg
works well alongside standard Go error handling - Test thoroughly: Make sure your error paths are covered by tests
Great for:
- Data processing pipelines
- API handlers with multiple validation steps
- File I/O operations
- Database transactions
- Any scenario with multiple sequential operations that can fail
Consider alternatives for:
- Simple functions with one or two error checks
- Performance-critical code (panic/recover has overhead)
- Libraries that need to expose traditional Go APIs
Feature | Traditional Go | rg |
---|---|---|
Error handling | Explicit if err != nil |
Automatic with Must |
Code length | Longer | Shorter |
Performance | Faster (no panic/recover) | Slightly slower |
Readability | Good for simple cases | Excellent for complex cases |
Debugging | Standard stack traces | Enhanced with hooks |
Guard(err *error, opts ...Option)
- Recover from panic and set errorMust0(err error)
- Panic if error is not nilMust[T](value T, err error) T
- Return value or panic on errorMust2
throughMust7
- Handle multiple return values
WithContext(ctx context.Context)
- Attach context to guard
OnGuard func(r any)
- Global panic hook (deprecated)OnGuardWithContext func(ctx context.Context, r any)
- Context-aware panic hook
We welcome contributions! Please feel free to submit issues, feature requests, or pull requests.
MIT License - see LICENSE file for details.
GUO YANKE - @yankeguo
β If you find this library helpful, please consider giving it a star!