A type-safe exception handling library based on Go generics that brings try-catch-like capabilities to Go.
- 🎯 Type-safe: Uses Go generics to ensure type-safe exception handling
- 🔗 Partial chaining: Supports chaining for
CatchAnyandFinally - 🏷️ Multiple error types: Built-in common error types (validation, database, network, business logic, auth, config, rate limit)
- 📊 Structured output: All errors support
ToMap()andToJSON()for easy parsing by agents/logs - 🔍 Rich context: Errors include file, line, function, timestamp, and stack trace
- 🔄 Finally support: Guarantees cleanup code execution
- 🎁 TryWithResult: Support for functions with return values and
OnSuccess/OnError/OrElsepatterns - 🐛 Debug mode: Optional debug logging for type matching issues
- 📦 Zero dependency: Pure Go implementation, no external dependencies
- 🚀 High performance: Built on Go's panic/recover with minimal overhead
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:
- A panic inside a Catch handler propagates immediately and skips any later
Finallycall. The chain is plain function calls, not a deferred block. Keep handler bodies non-panicking, or wrap them in their ownTry. - A panic inside
Finallyreplaces the original error — same as Go's nativedefer. IfFinallymust run fallible cleanup, guard it with its own recover. Trydoes not catch panics from goroutines started insidefn.recoveronly works within the same goroutine; an unhandled panic in a child goroutine still crashes the process.Catch[T]is an exact type assertion, noterrors.As.panic(&err)will not matchCatch[Err](align value vs pointer), and errors wrapped withfmt.Errorf("...: %w", err)will not be unwrapped. Catch the wrapper type, or panic the specific type you intend to catch. UseCatchAs/OnAsfor wrapping-aware matching.- Run semantics: a panic inside a handler propagates (after
Cleanupclauses have run, since they are deferred); a panic inside aCleanupitself replaces whatever was propagating, same as Go's nativedefer. Child goroutine panics are never captured —recoveris goroutine-local.
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:
- Unhandled panics come back as
error— silently swallowing is structurally impossible. Error panics pass through unchanged (soerrors.Is/Aswork end-to-end); other values are wrapped in*PanicError. - Cleanup always runs — declared inline, no forgotten
Finally. - Clauses are ordered values — no method/function chaining rules to memorize.
Run1covers return values:v, err := gotrycatch.Run1(fn, clauses...).
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 |
- Order Catch blocks by specificity: Most specific types first, generic types later
- Always use Finally: Ensure resource cleanup
- Use predefined error types: Prefer structured errors over raw strings/numbers
- Use CatchAny as fallback: Handle unexpected error types gracefully
- Enable debug mode for troubleshooting: Use
SetDebug(true)when type matching doesn't work as expected - Use ToJSON for logging: Structured output is easier to parse and analyze
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 |
Runallocates nothing on the happy path (no TryBlock is created) — cheaper thanTry- The unhandled-panic path pays one
*PanicError+ stack capture (error path only) - Fine for application-level flows; use
RunoverTryin hot loops
- Requires Go 1.21+ (generics;
panic(nil)detection semantics) - Fully compatible with the standard library
gotrycatch/errors→gotrycatch/errtypes(renamed to avoid clashing with stdliberrors). The old import path keeps compiling — it is now a pure alias layer with identical behavior (types are aliases; constructors are direct bindings, so File/Line/Stack attribution is unchanged).GetErrorType()output changes accordingly:errors.ValidationError→errtypes.ValidationError.CatchWithReturnremoved — replace withTryWithResult+CatchWithResult:
// v1
result, tb := gotrycatch.CatchWithReturn(tb, func(err string) interface{} { ... })
// v2
tb = gotrycatch.CatchWithResult[int, string](tb, func(err string) { ... })
result := tb.OrElse(defaultValue)- Everything else is additive:
Try/Catch/CatchAny/Finally/TryWithResultchains behave exactly as in v1.
- Can coexist with existing error-handling code
- Thread-safe for concurrent use
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:
- Basic usage
- Handling multiple error types
- Structured error output (ToMap/ToJSON)
- TryWithResult patterns
- Error chains (Unwrap/Is)
- A real-world multi-catch scenario
# 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 泛型确保异常处理的类型安全
- 🔗 部分链式调用: 支持
CatchAny和Finally的链式调用 - 🏷️ 七种异常类型: 内置常用异常类型(验证、数据库、网络、业务逻辑、认证、配置、限流)
- 📊 结构化输出: 所有错误支持
ToMap()和ToJSON(),便于 Agent 解析和日志记录 - 🔍 丰富上下文: 错误自动包含文件名、行号、函数名、时间戳和调用堆栈
- 🔄 Finally 支持: 保证清理代码的执行
- 🎁 TryWithResult: 支持带返回值的函数,提供
OnSuccess/OnError/OrElse/OrElseGet模式 - 🐛 调试模式: 可选的调试日志,帮助排查类型匹配问题
- ⚡ 断言辅助:
Assert和AssertNoError简化条件检查 - 📦 零依赖: 纯 Go 实现,无外部依赖
- 🚀 高性能: 基于 Go 的 panic/recover 机制,性能开销极小
tb.Catch[ErrorType](handler)。需要使用函数式调用:gotrycatch.Catch[ErrorType](tb, handler)。但是 CatchAny 和 Finally 方法支持链式调用。
在生产环境依赖本库之前,请先阅读以下边界:
- Catch handler 内部发生 panic 会立即向上传播,并跳过之后才调用的
Finally。调用链是普通函数调用而非 defer 块。请保证 handler 本身不 panic,或为其单独包一层Try。 Finally内部的 panic 会覆盖原始错误——与 Go 原生defer语义一致。若Finally中的清理可能失败,请自行 recover 保护。Try不捕获fn内启动的 goroutine 的 panic。recover只在同一 goroutine 内有效;子 goroutine 的未处理 panic 仍会崩溃整个进程。Catch[T]是精确类型断言,而非errors.As。panic(&err)无法匹配Catch[Err](注意值与指针对齐);用fmt.Errorf("...: %w", err)包装过的错误也不会被解包。请捕获包装类型,或直接 panic 你打算捕获的具体类型。需要穿透包装请用CatchAs/OnAs。- Run 语义:handler 内的 panic 会向外传播(但
Cleanup子句已通过 defer 注册,仍会执行);Cleanup自身的 panic 会覆盖正在传播的错误——与 Go 原生defer一致。子 goroutine 的 panic 永远无法被捕获——recover是 goroutine 局部的。
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
}为什么难以误用:
- 未处理的 panic 以
error返回——在结构上不可能静默吞错。error 型 panic 原样直通(errors.Is/As全链路可用);其他值包装为*PanicError。 - Cleanup 总是执行——内联声明,不存在忘记
Finally的问题。 - 子句是有序的值——无需记忆方法/函数混链规则。
Run1支持返回值:v, err := gotrycatch.Run1(fn, clauses...)。
推荐优先使用上面的 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) |
错误断言,有错误时抛出 |
- 按特定性排序 Catch 块: 将最具体的异常类型放在前面,通用类型放在后面
- 总是使用 Finally: 确保资源清理代码被执行
- 使用预定义异常类型: 优先使用库提供的异常类型,而不是原始字符串或数字
- 使用 CatchAny 作为兜底: 优雅地处理未预期的错误类型
- 调试时开启调试模式: 当类型匹配不符合预期时,使用
SetDebug(true)排查 - 日志使用 ToJSON: 结构化输出更易于解析和分析
使用 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 |
Run快乐路径零分配(不创建 TryBlock)——比Try更便宜- 未处理 panic 路径支付一次
*PanicError+ 堆栈捕获(仅错误路径) - 适合应用级流程;热循环优先用
Run而非Try
- 需要 Go 1.21+(泛型;
panic(nil)检测语义) - 与标准库完全兼容
gotrycatch/errors→gotrycatch/errtypes(改名以避免与标准库errors冲突)。旧导入路径仍可编译——现在是纯别名层,行为完全一致(类型为别名;构造函数为直接绑定,File/Line/Stack 归因不变)。GetErrorType()输出相应变化:errors.ValidationError→errtypes.ValidationError。CatchWithReturn已移除——用TryWithResult+CatchWithResult替代:
// v1
result, tb := gotrycatch.CatchWithReturn(tb, func(err string) interface{} { ... })
// v2
tb = gotrycatch.CatchWithResult[int, string](tb, func(err string) { ... })
result := tb.OrElse(defaultValue)- 其余全部是增量:
Try/Catch/CatchAny/Finally/TryWithResult链式行为与 v1 完全一致。
- 可以与现有的错误处理代码共存
- 支持并发安全使用
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 目录获取详细示例,包括:
- 基本用法演示
- 多种异常类型处理
- 结构化输出(ToMap/ToJSON)
- TryWithResult 模式
- 错误链(Unwrap/Is)
- 真实场景多 Catch 示例
# 演示程序(10个详细Demo)
go run ./cmd/demo
# 运行测试
go test -v ./...
# 查看覆盖率
go test -cover ./...
# 竞态检测
go test -race ./...MIT License