| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A type-safe exception handling library based on Go generics that brings try-catch-like capabilities to Go.
⚠️ Chaining limitation: Due to Go's limitation that methods cannot have generic type parameters, you cannot write tb.Catch[ErrorType](handler). Use the functional form instead: gotrycatch.Catch[ErrorType](tb, handler). CatchAny and Finally do support chaining.
Read these before relying on this library in production:
go get github.com/linkerlin/gotrycatchRun is the recommended entry point. It is one call, impossible to forget cleanup, and results flow through ordinary Go error handling:
import (
"fmt"
"github.com/linkerlin/gotrycatch"
"github.com/linkerlin/gotrycatch/errtypes"
)
func loadUser(id string) error {
err := gotrycatch.Run(func() { queryUser(id) },
// errors.As semantics: matches through fmt.Errorf("%w", ...) wrappers
gotrycatch.OnAs(func(e errtypes.DatabaseError) {
retry(id)
}),
gotrycatch.On(func(e errtypes.RateLimitError) {
wait(e.RetryAfter)
}),
// always runs, LIFO, even if a handler panics
gotrycatch.Cleanup(func() { conn.Close() }),
)
if err != nil {
return fmt.Errorf("load user %s: %w", id, err)
}
return nil
}Why it is hard to misuse:
Prefer Run above; the classic API remains fully supported:
package main
import (
"fmt"
"github.com/linkerlin/gotrycatch"
"github.com/linkerlin/gotrycatch/errtypes"
)
func main() {
tb := gotrycatch.Try(func() {
// Code that may panic
gotrycatch.Throw(errtypes.NewValidationError("email", "invalid format", 1001))
})
tb = gotrycatch.Catch[errtypes.ValidationError](tb, func(err errtypes.ValidationError) {
fmt.Printf("Validation error: %s (field: %s, code: %d)\n", err.Message, err.Field, err.Code)
})
tb.Finally(func() {
fmt.Println("Cleanup done")
})
}// Execute function that returns a value
tb := gotrycatch.TryWithResult(func() int {
return computeValue()
})
// Success callback
tb.OnSuccess(func(result int) {
fmt.Println("Result:", result)
})
// Error callback
tb.OnError(func(err interface{}) {
fmt.Println("Error:", err)
})
// Get result with default value
result := tb.OrElse(0)
// Or lazy evaluation of default
result := tb.OrElseGet(func() int { return computeDefault() })tb := gotrycatch.Try(func() {
processUserData()
})
tb = gotrycatch.Catch[errors.ValidationError](tb, func(err errors.ValidationError) {
fmt.Printf("Validation failed: %s\n", err.Message)
})
tb = gotrycatch.Catch[errors.DatabaseError](tb, func(err errors.DatabaseError) {
fmt.Printf("Database error: %s on table %s\n", err.Operation, err.Table)
})
tb = gotrycatch.Catch[errors.NetworkError](tb, func(err errors.NetworkError) {
if err.Timeout {
fmt.Printf("Network timeout: %s\n", err.URL)
} else {
fmt.Printf("Network error %d: %s\n", err.StatusCode, err.URL)
}
})
tb = tb.CatchAny(func(err interface{}) {
fmt.Printf("Unknown error: %v\n", err)
})
tb.Finally(func() {
fmt.Println("Processing done")
})tb := gotrycatch.Try(func() {
riskyOperation()
})
// Query state
if tb.HasError() {
fmt.Printf("Error type: %s\n", tb.GetErrorType())
fmt.Printf("Error value: %v\n", tb.GetError())
}
if !tb.IsHandled() {
// Decide how to handle based on error type
switch tb.GetErrorType() {
case "errors.ValidationError":
// Handle validation error
default:
tb = tb.CatchAny(func(err interface{}) {
logUnknownError(err)
})
}
}
// Enable debug mode to trace type matching
gotrycatch.SetDebug(true)tb := gotrycatch.Catch[errors.BusinessLogicError](tb, func(err errors.BusinessLogicError) {
// JSON output for logging/agents
jsonData, _ := err.ToJSON()
log.Printf("ERROR: %s", string(jsonData))
// Output: {"type":"BusinessLogicError","rule":"inventory_check","details":"Out of stock","file":"main.go","line":42,"function":"processOrder","timestamp":"2024-01-15T10:30:00Z","stack":[...]}
})// Assert condition, throw error if false
gotrycatch.Assert(value != "", errors.NewValidationError("value", "cannot be empty", 1001))
// Assert no error, wrap and throw if error exists
gotrycatch.AssertNoError(err, "database operation failed")All error types include: File, Line, Function, Timestamp, Stack
| Type | Specific Fields | Constructor | Use Case |
|---|---|---|---|
| ValidationError | Field, Message, Code | NewValidationError(field, message, code) | Data validation errors |
| DatabaseError | Operation, Table, Cause | NewDatabaseError(operation, table, cause) | Database operation errors |
| NetworkError | URL, StatusCode, Timeout | NewNetworkError(url, code) | HTTP errors |
| NetworkError | URL, Timeout | NewNetworkTimeoutError(url) | Network timeouts |
| BusinessLogicError | Rule, Details | NewBusinessLogicError(rule, details) | Business rule violations |
| ConfigError | Key, Value, Reason | NewConfigError(key, value, reason) | Configuration errors |
| AuthError | Operation, User, Reason | NewAuthError(operation, user, reason) | Authentication/authorization errors |
| RateLimitError | Resource, Limit, Current, RetryAfter | NewRateLimitError(resource, limit, current, retryAfter) | Rate limiting errors |
var err errors.ValidationError
err.Error() // string - Full error description with location
err.ToMap() // map[string]interface{} - Structured data
err.ToJSON() // ([]byte, error) - JSON output
err.Unwrap() // error - Underlying error (DatabaseError returns Cause)
err.Is(target) // bool - Error matching| Function/Method | Signature | Description |
|---|---|---|
| Run | func Run(fn func(), clauses ...Clause) error | v2 Idiomatic clause-based execution; returns unhandled panics as error |
| Run1 | func Run1[T any](fn func() T, clauses ...Clause) (T, error) | v2 Run with a return value |
| On[E] | func On[E any](handler func(E)) Clause | v2 Clause: exact-type match (Catch semantics) |
| OnAs[E] | func OnAs[E error](handler func(E)) Clause | v2 Clause: errors.As match, penetrates %w wrapping |
| Any | func Any(handler func(interface{})) Clause | v2 Clause: fallback for any panic |
| Cleanup | func Cleanup(fn func()) Clause | v2 Clause: always runs, LIFO, even if a handler panics |
| Try | func Try(fn func()) *TryBlock | Execute function and capture any panic |
| Catch[T] | func Catch[T any](tb *TryBlock, handler func(T)) *TryBlock | Handle panics of exact type T |
| CatchAs[E] | func CatchAs[E error](tb *TryBlock, handler func(E)) *TryBlock | v2 Handle via errors.As (penetrates wrapping, E or *E) |
| CatchAny | func (tb *TryBlock) CatchAny(handler func(interface{})) *TryBlock | Handle any unhandled panic |
| Finally | func (tb *TryBlock) Finally(fn func()) | Execute cleanup code |
| Method | Return Type | Description |
|---|---|---|
| HasError() | bool | Whether a panic was captured |
| GetError() | interface{} | Get the panic value |
| Err() | error | v2 Panic bridged to error (errors.Is/As-ready; non-error panics become *PanicError) |
| GetErrorType() | string | Get error type name (e.g., "errtypes.ValidationError") |
| CanonicalErrorType() | string | v2 Pointer-free short type name (e.g., "ValidationError") |
| IsHandled() | bool | Whether error was handled |
| String() | string | Friendly string representation |
| Function/Method | Signature | Description |
|---|---|---|
| TryWithResult | func TryWithResult[T any](fn func() T) *TryBlockWithResult[T] | Execute function with return value |
| CatchWithResult | func CatchWithResult[T, E any](tb *TryBlockWithResult[T], handler func(E)) *TryBlockWithResult[T] | Typed catch for TryWithResult |
| CatchAnyWithResult | func CatchAnyWithResult[T any](tb *TryBlockWithResult[T], handler func(interface{})) *TryBlockWithResult[T] | Catch any for TryWithResult |
| GetResult() | T | Get the result value |
| OnSuccess | func (tb *TryBlockWithResult[T]) OnSuccess(fn func(T)) *TryBlockWithResult[T] | Callback on success |
| OnError | func (tb *TryBlockWithResult[T]) OnError(fn func(interface{})) *TryBlockWithResult[T] | Callback on error |
| OrElse | func (tb *TryBlockWithResult[T]) OrElse(defaultValue T) T | Get result or default |
| OrElseGet | func (tb *TryBlockWithResult[T]) OrElseGet(supplier func() T) T | Get result or lazy default |
| Function | Signature | Description |
|---|---|---|
| SetDebug | func SetDebug(enabled bool) | Enable/disable debug logging |
| IsDebug | func IsDebug() bool | Check debug mode status |
| Throw | func Throw(err interface{}) | Throw an exception (panic) |
| Assert | func Assert(condition bool, err interface{}) | Assert condition, throw if false |
| AssertNoError | func AssertNoError(err error, msg string) | Assert no error, throw with message if error |
Measured with go test -bench . (see gotrycatch_bench_test.go; i9-13900HX, indicative only):
| Scenario | ns/op | allocs/op |
|---|---|---|
| Error return (idiomatic baseline) | ~0.2 | 0 |
| Raw recover, no panic | ~2 | 0 |
| Run, no panic | ~11 | 0 |
| Run1, no panic | ~9 | 0 |
| Try, no panic | ~63 | 1 |
| TryWithResult + OrElse, no panic | ~66 | 1 |
| Run + panic + On hit | ~200 | 0 |
| Raw recover, panic path | ~157 | 0 |
| Try + panic + Catch hit | ~469 | 1 |
| Run unhandled panic → error | ~1385 | 2 |
// v1
result, tb := gotrycatch.CatchWithReturn(tb, func(err string) interface{} { ... })
// v2
tb = gotrycatch.CatchWithResult[int, string](tb, func(err string) { ... })
result := tb.OrElse(defaultValue)A: Because methods cannot have generic type parameters in Go. So this is not supported:
// ❌ Not supported
tb := gotrycatch.Try(func() { ... }).Catch[ErrorType](handler)Use the functional form instead:
// ✅ Correct
tb := gotrycatch.Try(func() { ... })
tb = gotrycatch.Catch[ErrorType](tb, handler)But CatchAny and Finally support chaining:
// ✅ Supported
tb.CatchAny(handler).Finally(cleanup)A: Enable debug mode:
gotrycatch.SetDebug(true)
// Output: [gotrycatch] Catch: type errors.ValidationError does not match target type int
// Output: [gotrycatch] Catch: type errors.ValidationError matched, calling handlerA: Unhandled errors are re-thrown after Finally executes. Always use CatchAny as a fallback if you don't want panics to propagate.
See the cmd/demo directory for more:
# Demo (10 detailed demos)
go run ./cmd/demo
# Run tests
go test -v ./...
# Run with coverage
go test -cover ./...
# Run with race detector
go test -race ./...MIT License
一个基于 Go 泛型的类型安全异常处理库,为 Go 带来类似 try-catch 的异常处理能力。
⚠️ 链式调用限制: 由于 Go 语言的限制,方法不能有泛型类型参数,因此不能直接写 tb.Catch[ErrorType](handler)。需要使用函数式调用:gotrycatch.Catch[ErrorType](tb, handler)。但是 CatchAny 和 Finally 方法支持链式调用。
在生产环境依赖本库之前,请先阅读以下边界:
go get github.com/linkerlin/gotrycatchRun 是 v2 的推荐入口。一次调用、不可能忘记清理、结果直接进入 Go 惯用的 error 流:
import (
"fmt"
"github.com/linkerlin/gotrycatch"
"github.com/linkerlin/gotrycatch/errtypes"
)
func loadUser(id string) error {
err := gotrycatch.Run(func() { queryUser(id) },
// errors.As 语义:可穿透 fmt.Errorf("%w", ...) 包装
gotrycatch.OnAs(func(e errtypes.DatabaseError) {
retry(id)
}),
gotrycatch.On(func(e errtypes.RateLimitError) {
wait(e.RetryAfter)
}),
// 总是执行、LIFO,handler panic 也不会跳过
gotrycatch.Cleanup(func() { conn.Close() }),
)
if err != nil {
return fmt.Errorf("load user %s: %w", id, err)
}
return nil
}为什么难以误用:
推荐优先使用上面的 Run;经典 API 继续完整支持:
package main
import (
"fmt"
"github.com/linkerlin/gotrycatch"
"github.com/linkerlin/gotrycatch/errtypes"
)
func main() {
tb := gotrycatch.Try(func() {
// 可能会 panic 的代码
gotrycatch.Throw(errtypes.NewValidationError("email", "格式无效", 1001))
})
tb = gotrycatch.Catch[errtypes.ValidationError](tb, func(err errtypes.ValidationError) {
fmt.Printf("验证错误: %s (字段: %s, 代码: %d)\n", err.Message, err.Field, err.Code)
})
tb.Finally(func() {
fmt.Println("清理工作完成")
})
}// 执行带返回值的函数
tb := gotrycatch.TryWithResult(func() int {
return computeValue()
})
// 成功回调
tb.OnSuccess(func(result int) {
fmt.Println("结果:", result)
})
// 错误回调
tb.OnError(func(err interface{}) {
fmt.Println("错误:", err)
})
// 获取结果,有错误时返回默认值
result := tb.OrElse(0)
// 或者延迟计算默认值
result := tb.OrElseGet(func() int { return computeDefault() })tb := gotrycatch.Try(func() {
processUserData()
})
tb = gotrycatch.Catch[errors.ValidationError](tb, func(err errors.ValidationError) {
fmt.Printf("验证失败: %s\n", err.Message)
})
tb = gotrycatch.Catch[errors.DatabaseError](tb, func(err errors.DatabaseError) {
fmt.Printf("数据库错误: %s on table %s\n", err.Operation, err.Table)
})
tb = gotrycatch.Catch[errors.NetworkError](tb, func(err errors.NetworkError) {
if err.Timeout {
fmt.Printf("网络超时: %s\n", err.URL)
} else {
fmt.Printf("网络错误 %d: %s\n", err.StatusCode, err.URL)
}
})
tb = tb.CatchAny(func(err interface{}) {
fmt.Printf("未知错误: %v\n", err)
})
tb.Finally(func() {
fmt.Println("处理完成")
})tb := gotrycatch.Try(func() {
riskyOperation()
})
// 查询状态
if tb.HasError() {
fmt.Printf("错误类型: %s\n", tb.GetErrorType())
fmt.Printf("错误值: %v\n", tb.GetError())
}
if !tb.IsHandled() {
// 根据错误类型决定处理方式
switch tb.GetErrorType() {
case "errors.ValidationError":
// 处理验证错误
default:
tb = tb.CatchAny(func(err interface{}) {
logUnknownError(err)
})
}
}
// 开启调试模式追踪类型匹配
gotrycatch.SetDebug(true)tb := gotrycatch.Catch[errors.BusinessLogicError](tb, func(err errors.BusinessLogicError) {
// JSON 输出便于日志和 Agent 解析
jsonData, _ := err.ToJSON()
log.Printf("ERROR: %s", string(jsonData))
// 输出: {"type":"BusinessLogicError","rule":"inventory_check","details":"库存不足","file":"main.go","line":42,"function":"processOrder","timestamp":"2024-01-15T10:30:00Z","stack":[...]}
})// 条件断言,false 时抛出错误
gotrycatch.Assert(value != "", errors.NewValidationError("value", "不能为空", 1001))
// 错误断言,有错误时包装并抛出
gotrycatch.AssertNoError(err, "数据库操作失败")所有错误类型都包含:File、Line、Function、Timestamp、Stack
| 类型 | 专有字段 | 构造函数 | 用途 |
|---|---|---|---|
| ValidationError | Field, Message, Code | NewValidationError(field, message, code) | 数据验证错误 |
| DatabaseError | Operation, Table, Cause | NewDatabaseError(operation, table, cause) | 数据库操作错误 |
| NetworkError | URL, StatusCode, Timeout | NewNetworkError(url, code) | HTTP 错误 |
| NetworkError | URL, Timeout | NewNetworkTimeoutError(url) | 网络超时 |
| BusinessLogicError | Rule, Details | NewBusinessLogicError(rule, details) | 业务规则违规 |
| ConfigError | Key, Value, Reason | NewConfigError(key, value, reason) | 配置错误 |
| AuthError | Operation, User, Reason | NewAuthError(operation, user, reason) | 认证授权错误 |
| RateLimitError | Resource, Limit, Current, RetryAfter | NewRateLimitError(resource, limit, current, retryAfter) | 限流错误 |
var err errors.ValidationError
err.Error() // string - 完整错误描述(含位置信息)
err.ToMap() // map[string]interface{} - 结构化数据
err.ToJSON() // ([]byte, error) - JSON 输出
err.Unwrap() // error - 底层错误(DatabaseError 返回 Cause)
err.Is(target) // bool - 错误匹配| 函数/方法 | 签名 | 说明 |
|---|---|---|
| Try | func Try(fn func()) *TryBlock | 执行函数并捕获任何 panic |
| 函数/方法 | 签名 | 说明 |
| ----------- | ------ | ------ |
| Run | func Run(fn func(), clauses ...Clause) error | v2 惯用法子句式执行;未处理 panic 以 error 返回 |
| Run1 | func Run1[T any](fn func() T, clauses ...Clause) (T, error) | v2 带返回值的 Run |
| On[E] | func On[E any](handler func(E)) Clause | v2 子句:精确类型匹配(Catch 语义) |
| OnAs[E] | func OnAs[E error](handler func(E)) Clause | v2 子句:errors.As 匹配,穿透 %w 包装 |
| Any | func Any(handler func(interface{})) Clause | v2 子句:任意 panic 兜底 |
| Cleanup | func Cleanup(fn func()) Clause | v2 子句:总是执行、LIFO、handler panic 也不跳过 |
| Try | func Try(fn func()) *TryBlock | 执行函数并捕获 panic |
| Catch[T] | func Catch[T any](tb *TryBlock, handler func(T)) *TryBlock | 处理精确类型 T 的异常 |
| CatchAs[E] | func CatchAs[E error](tb *TryBlock, handler func(E)) *TryBlock | v2 errors.As 匹配(穿透包装、E 或 *E) |
| CatchAny | func (tb *TryBlock) CatchAny(handler func(interface{})) *TryBlock | 处理任何未处理的异常 |
| Finally | func (tb *TryBlock) Finally(fn func()) | 执行清理代码 |
| 方法 | 返回类型 | 说明 |
|---|---|---|
| HasError() | bool | 是否捕获了错误 |
| GetError() | interface{} | 获取错误值 |
| Err() | error | v2 panic 桥接为 error(支持 errors.Is/As;非 error panic 包装为 *PanicError) |
| GetErrorType() | string | 获取错误类型名(如 "errtypes.ValidationError") |
| CanonicalErrorType() | string | v2 去指针短类型名(如 "ValidationError") |
| IsHandled() | bool | 错误是否已被处理 |
| String() | string | 友好的字符串表示 |
| 函数/方法 | 签名 | 说明 |
|---|---|---|
| TryWithResult | func TryWithResult[T any](fn func() T) *TryBlockWithResult[T] | 执行带返回值的函数 |
| CatchWithResult | func CatchWithResult[T, E any](tb *TryBlockWithResult[T], handler func(E)) *TryBlockWithResult[T] | TryWithResult 的类型捕获 |
| CatchAnyWithResult | func CatchAnyWithResult[T any](tb *TryBlockWithResult[T], handler func(interface{})) *TryBlockWithResult[T] | TryWithResult 的任意捕获 |
| GetResult() | T | 获取结果值 |
| OnSuccess | func (tb *TryBlockWithResult[T]) OnSuccess(fn func(T)) *TryBlockWithResult[T] | 成功时回调 |
| OnError | func (tb *TryBlockWithResult[T]) OnError(fn func(interface{})) *TryBlockWithResult[T] | 错误时回调 |
| OrElse | func (tb *TryBlockWithResult[T]) OrElse(defaultValue T) T | 获取结果或默认值 |
| OrElseGet | func (tb *TryBlockWithResult[T]) OrElseGet(supplier func() T) T | 获取结果或延迟计算默认值 |
| 函数 | 签名 | 说明 |
|---|---|---|
| SetDebug | func SetDebug(enabled bool) | 开启/关闭调试日志 |
| IsDebug | func IsDebug() bool | 查询调试模式状态 |
| Throw | func Throw(err interface{}) | 抛出异常(panic) |
| Assert | func Assert(condition bool, err interface{}) | 条件断言,false 时抛出 |
| AssertNoError | func AssertNoError(err error, msg string) | 错误断言,有错误时抛出 |
使用 go test -bench . 实测(见 gotrycatch_bench_test.go;i9-13900HX,仅供参考):
| 场景 | ns/op | allocs/op |
|---|---|---|
| error 返回值(Go 惯用基线) | ~0.2 | 0 |
| 原生 recover,无 panic | ~2 | 0 |
| Run,无 panic | ~11 | 0 |
| Run1,无 panic | ~9 | 0 |
| Try,无 panic | ~63 | 1 |
| TryWithResult + OrElse,无 panic | ~66 | 1 |
| Run + panic + On 命中 | ~200 | 0 |
| 原生 recover,panic 路径 | ~157 | 0 |
| Try + panic + Catch 命中 | ~469 | 1 |
| Run 未处理 panic → error | ~1385 | 2 |
// v1
result, tb := gotrycatch.CatchWithReturn(tb, func(err string) interface{} { ... })
// v2
tb = gotrycatch.CatchWithResult[int, string](tb, func(err string) { ... })
result := tb.OrElse(defaultValue)A: 由于 Go 语言的限制,方法不能有泛型类型参数。因此不能写:
// ❌ 这样写是不支持的
tb := gotrycatch.Try(func() { ... }).Catch[ErrorType](handler)只能使用函数式调用:
// ✅ 正确的写法
tb := gotrycatch.Try(func() { ... })
tb = gotrycatch.Catch[ErrorType](tb, handler)但是 CatchAny 和 Finally 方法支持链式调用:
// ✅ 这样是可以的
tb.CatchAny(handler).Finally(cleanup)A: 开启调试模式:
gotrycatch.SetDebug(true)
// 输出: [gotrycatch] Catch: type errors.ValidationError does not match target type int
// 输出: [gotrycatch] Catch: type errors.ValidationError matched, calling handlerA: 未处理的错误会在 Finally 执行后重新抛出。如果不想让 panic 传播,请使用 CatchAny 作为兜底。
查看 cmd/demo 目录获取详细示例,包括:
# 演示程序(10个详细Demo)
go run ./cmd/demo
# 运行测试
go test -v ./...
# 查看覆盖率
go test -cover ./...
# 竞态检测
go test -race ./...MIT License
| Back | FazBrowse Home | New Git URL |