template/pkg/result/doc.go
djmil 13f6a6812a result.Wrap - propagate a failed Expect into a new type U
- public API streamline
- Failf[T]("msg") -	originate a failure from a message; embed a cause with %w
- Err[T](err) - sets .err verbatim (the return zero, err / sentinel case)
2026-06-14 11:43:23 +00:00

93 lines
3.1 KiB
Go

// Package result provides a generic Expect[T] type that supports two error
// handling styles without forcing either one.
//
// # Two modes, one type
//
// Expect[T] is a drop-in replacement for (T, error) that also enables
// panic-based happy-path propagation when that suits the code better. Both
// styles compose freely — the same Expect[T] value works in either.
//
// func parseHost(s string) result.Expect[string] {
// if s == "" {
// return result.Failf[string]("host must not be empty")
// }
// return result.Ok(s)
// }
//
// Mode 1 — standard Go style (if err != nil):
//
// host, err := parseHost(s).Unwrap()
// if err != nil {
// return 0, err
// }
//
// Or check and access separately, just as with (T, error):
//
// r := parseHost(s)
// if r.Err() != nil {
// return 0, r.Err()
// }
// use(r.Value())
//
// Mode 2 — happy-path style (panic-based propagation):
//
// port := parseHost(s).Expect("parse host") // panics on failure
//
// Failures are collected at the entry point by [Go] or [Run] and returned as a
// normal Go error — no goroutine leaks, no silent swallowing.
//
// # Layering rule
//
// Reusable library code (packages under pkg/) must only *return* Expect[T] —
// it must never call .Expect(), .Must(), or .Expectf() itself. Those methods
// exit the current goroutine and are only safe inside a goroutine controlled
// by [Go] or [Run].
//
// The right split:
//
// - pkg/ functions: return Expect[T] — let the caller decide how to handle it.
// - Application code (cmd/, HTTP handlers, …): chain .Expect() calls freely,
// protected by a defer result.Catch(&err) or a result.Run wrapper.
//
// Stack traces are captured at the failure site and can be retrieved from the
// collected error via [StackTrace].
//
// # Constructors
//
// [Ok] and [Err] are the two field constructors (a success value or a bare
// error). On top of them, [Failf] originates a failure from a message (embed a
// cause with %w), [Wrap] propagates an already-failed result into a new type,
// and [Of] bridges existing (value, error) return signatures:
//
// data := result.Of(os.ReadFile("cfg.json")).Expect("read config")
//
// # Boundary pattern
//
// func run() error {
// return result.Run(func() {
// host := parseHost(cfg.Host).Expect("load config host")
// _ = host // happy path continues …
// })
// }
//
// [Go] is the typed variant — it returns Expect[T] when the closure produces
// a value. [Run] is a convenience wrapper for closures that return nothing.
//
// [Catch] is an alternative boundary for use with named error returns:
//
// func load() (err error) {
// defer result.Catch(&err)
// host := parseHost(cfg.Host).Expect("load config host")
// _ = host
// return
// }
//
// Important: [Catch] relies on recover() and only works with the default
// (panic) build. With -tags result_goexit, Expect and Expectf exit via
// runtime.Goexit which recover() cannot intercept — use [Run] or [Go] instead,
// as they work correctly in both builds.
//
// Genuine runtime panics (nil-pointer dereferences, index out of bounds, etc.)
// are not recovered — they still crash the program, as they should.
package result