diff --git a/README.md b/README.md index f2a2ad0..843aad9 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,24 @@ if err != nil { } ``` +**`WithStack2[T any](v T, err error) (T, error)`** (Go 1.18+) + +Adds a stack frame at the current location, while passing through a return value. This is useful for adding stack traces to errors from functions that return multiple values (value, error) in a single line. + +If err is nil, returns (v, nil) unchanged. If err is not nil, returns (v, err_with_stack). + +```go +// Instead of this: +result, err := externalLib.DoSomething() +if err != nil { + return result, errors.WithStack(err) +} +return result, nil + +// You can write this: +return errors.WithStack2(externalLib.DoSomething()) +``` + **`WithFrame(err error, depth int) error`** Adds a stack frame at a specific depth in the call stack. diff --git a/example_stacktrace_test.go b/example_stacktrace_test.go index 4364cfc..9994d36 100644 --- a/example_stacktrace_test.go +++ b/example_stacktrace_test.go @@ -9,21 +9,54 @@ import ( func ExampleWithStack() { // Simulate an error from an external library externalErr := fmt.Errorf("external library error") - + // Add stack trace at current location err := errors.WithStack(externalErr) - + fmt.Println(err) // Output: external library error } +// Result represents a simple computation result +type Result struct { + Value int +} + +// mockCompute simulates an external library function +func mockCompute(input int) (Result, error) { + if input < 0 { + return Result{}, fmt.Errorf("negative input not allowed") + } + return Result{Value: input * 2}, nil +} + +func ExampleWithStack2() { + // Demonstrates using WithStack2 to add stack traces inline while returning values + compute := func(input int) (Result, error) { + // WithStack2 adds stack trace in a single line + return errors.WithStack2(mockCompute(input)) + } + + // Success case - error is nil + result, err := compute(5) + fmt.Printf("Result: %+v, Error: %v\n", result, err) + + // Error case - error gets stack trace added + result, err = compute(-1) + fmt.Printf("Result: %+v, Error: %v\n", result, err) + + // Output: + // Result: {Value:10}, Error: + // Result: {Value:0}, Error: negative input not allowed +} + func ExampleWithFrame() { // This helper function wraps errors with the caller's location wrapError := func(err error) error { // Skip 1 frame to capture the caller's location instead of this function return errors.WithFrame(err, 1) } - + err := wrapError(fmt.Errorf("original error")) fmt.Println(err) // Output: original error diff --git a/stacktrace.go b/stacktrace.go index f656670..d9c1b5d 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -7,6 +7,28 @@ func WithStack(err error) error { return WithFrame(err, 1) } +// WithStack2 wraps an error with a stack frame captured at the call site, while +// passing through a return value. This is useful for adding stack traces to errors +// from functions that return multiple values (value, error). +// +// If err is nil, returns (v, nil) unchanged. If err is not nil, returns (v, err_with_stack) +// where err_with_stack includes the stack frame. +// +// Example: +// +// return errors.WithStack2(externalLib.DoSomething()) +// +// This is equivalent to but more concise than: +// +// result, err := externalLib.DoSomething() +// if err != nil { +// return result, errors.WithStack(err) +// } +// return result, nil +func WithStack2[T any](v T, err error) (T, error) { + return v, WithFrame(err, 1) +} + // WithFrame wraps an error with a stack frame at the specified depth in the call stack. // The depth parameter indicates how many stack frames to skip (0 = current frame). func WithFrame(err error, d int) error {