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 != nilchecks
- β 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/rgTransform 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 error
- rg.Must(value, err)- For functions returning one value + error
- rg.Must2(v1, v2, err)- For functions returning two values + error
- ... up to rg.Must7for 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: rgworks 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 error
- Must0(err error)- Panic if error is not nil
- Must[T](value T, err error) T- Return value or panic on error
- Must2through- Must7- 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!