samber/mo value and computation types
Read go.mod, imports, public signatures, conversions, serialization, database scans, and tests before changing a mo type. Confirm APIs in the pinned release; method and subpackage surfaces evolve independently.
Choose the value model
| Type | Use when | Boundary question |
|---|---|---|
Option[T] |
A value can be present or absent without failure | Is empty distinct from the zero value? |
Result[T] |
A computation has a success value or Go error | Must callers retain errors.Is/errors.As? |
Either[L, R] |
Both alternatives are meaningful domain values | Which side convention is already established? |
Either3 through Either5 |
A closed set needs more variants | Would a named domain type communicate the cases better? |
Future, IO, Task, State families |
Existing code relies on their evaluation model | Who starts, waits for, cancels, or owns the computation? |
Give nested types an explicit meaning. Result[Option[T]], for example, can distinguish failure from a successful absence, but only when callers preserve both cases.
Constructors and extraction
Representative constructors include Some, None, TupleToOption, PointerToOption, Ok, Err, TupleToResult, Left, and Right. Verify the exact generic arguments and conversion helpers before use.
Prefer checked extraction such as Get, OrElse, matching, or conversion back to ordinary Go returns. MustGet panics for the absent or error case. If code uses mo.Do to translate those panics into a Result, confirm which panic values it catches in the selected version and keep unrelated panics distinguishable.
Same-type methods versus pipeline packages
Go methods cannot introduce new type parameters. In mo versions that follow this design:
- direct
MapandFlatMapmethods keep the same contained type; github.com/samber/mo/option,/result, and/eitherexpose standalone functions for type-changing transforms;PipeNhelpers compose several typed stages;/either3,/either4, and/either5provide the corresponding multi-variant transforms.
Use the direct method for a same-type step and a subpackage function when T changes to U. Check the pinned API for callback shapes: a direct method may accept a boolean, error, or wrapped return where the similarly named subpackage function does not.
Preserve failure and boundary behavior
When wrapping (T, error), retain the original error so inspection still works after extraction. Define whether absence maps to nil, a sentinel, an HTTP status, a database NULL, or another domain result.
Serialization and database support are type- and version-specific. Confirm implementations of json.Marshaler, json.Unmarshaler, sql.Scanner, or driver.Valuer rather than assuming every mo type supports every boundary. Test omitted, null, zero, malformed, and round-trip cases required by the external contract.
Future-like and lazy types need an explicit start and completion model. Verify whether work begins at construction, subscription, or Run; whether callbacks can run concurrently; and how cancellation reaches underlying I/O. A context stored beside a future is not cancellation unless the operation observes it.
Keep pipelines reviewable
Use matching or an explicit branch where recovery, logging, cancellation, or partial results need distinct handling. Avoid repeated wrap/unwrap conversions across package boundaries; convert once at the boundary that owns the representation.
Verification
Test every meaningful variant, the type-changing stages, zero values, extraction failure, error identity, and serialization or database behavior. For Future, Task, or related types, cover completion, rejection, timeout/cancellation, repeated observation, and goroutine cleanup without relying on narrow sleeps.