// 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.Errf[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 // // The rule is simple: .Expect() is safe anywhere a boundary ([Go] or [Run]) // owns the goroutine. In practice: // // - pkg/ functions that just compute and return: return Expect[T], let the // caller decide how to handle it. // - pkg/ functions that internally spawn goroutines via [Go] or [Run]: they // own those goroutines and may freely chain .Expect() inside them. From // the outside they still look like normal functions returning Expect[T]. // - Application code (cmd/, HTTP handlers, …): chain .Expect() freely, // protected by a [Run] wrapper or defer [Catch]. // // Stack traces are captured at the failure site and can be retrieved from the // collected error via [StackTrace]. // // # Constructors // // Use [Ok] to wrap a success value, [Err] / [Errf] / [Errw] to wrap errors, // and [Of] to bridge 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. // // # Concurrent pattern // // Combining [Async] with the boundary pattern makes concurrent code almost as // readable as sequential code. Fire goroutines with [Async], then collect with // [All] or by reading channels individually — failures surface as normal errors // at the boundary, with no manual WaitGroups, mutex guards, or error channels: // // func fetchAll(urls []string) ([]string, error) { // return result.Map(urls, func(url string) string { // return fetch(url).Expect("fetch") // happy path inside the goroutine // }).Unwrap() // } // // For heterogeneous concurrent work use [AsyncOf] — it accepts functions that // already return Expect[T] (as library functions should), so only one .Expect() // per goroutine is needed at the collection site: // // func loadConfig() (Config, error) { // hostCh := result.AsyncOf(resolveHost) // resolveHost() Expect[string] // portCh := result.AsyncOf(resolvePort) // resolvePort() Expect[int] // return result.Go(func() Config { // host := (<-hostCh).Expect("resolve host") // port := (<-portCh).Expect("resolve port") // return Config{Host: host, Port: port} // }).Unwrap() // } // // Genuine runtime panics (nil-pointer dereferences, index out of bounds, etc.) // are not recovered — they still crash the program, as they should. package result