# samber/mo: Option, Result and Either Monads for Go 1.18+ Generics

> samber/mo packages Option, Result, Either, Future, IO, Task and State types for Go, built on 1.18 generics and the standard library alone. It is a small, focused dependency, but it will not replace idiomatic error handling and it is not a concurrency framework.

**samber/mo** — 🦄  Monads and popular FP abstractions, powered by Go 1.18+ Generics (Option, Result, Either...)

- Repository: https://github.com/samber/mo
- Website: https://pkg.go.dev/github.com/samber/mo
- Stars: 3,424 · Forks: 120
- Language: Go
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/samber-mo

## What samber/mo solves, and who actually needs it

Go has no built-in Option or Result type. The convention is a value plus an error, a pointer that may be nil, or a comma-ok map lookup. That works, but it spreads absence checks across call sites and makes chained transformations verbose. samber/mo, described in its README as bringing "monads and popular FP abstractions to Go projects" on Go 1.18+ generics, packages the containers that other languages ship in their standard libraries.

The README lists ten types: Option[T] (also called Maybe), Result[T], Either[A, B], EitherX with X between 3 and 5, Future[T], IO[T], IOEither[T], Task[T], TaskEither[T] and State[S, A]. The intended audience is a Go developer who has used Scala, Rust or fp-ts and wants Option and Either in Go without writing them per project. The README states the library was inspired by Scala, Rust and FP-TS, and that it has no dependencies except the Go standard library, which is the strongest practical argument for it: adding mo to a module does not pull a transitive tree into your go.sum.

It is not for teams that consider Go's explicit error returns a feature to be preserved. mo gives you a second way to express failure, and mixing the two styles in one codebase is a real cost.

## How the Option type is built and how values flow through it

Option[T] is a container for an optional value of type T. The README states that if the value exists the Option is Some, and if it is absent the Option is None. The repository layout shows this is implemented in option.go at the module root, with a companion option/ subdirectory that holds the composition helpers, and option_go118.go and option_go122.go for version-specific code paths. The root also contains either.go, either3.go through either5.go, future.go, io.go, io_either.go, state.go, task.go and task_either.go, each with a matching subdirectory for sub-package helpers.

Construction is explicit. The README documents mo.Some(), mo.None(), and conversion helpers mo.TupleToOption(), mo.EmptyableToOption() and mo.PointerToOption(). The last two matter in real Go code: EmptyableToOption maps a zero value to None, and PointerToOption maps a nil pointer to None, which is how you bridge existing APIs that signal absence with nil.

Transformation is where the type earns its place. Option has Map, FlatMap, Match, OrElse, OrEmpty, ForEach, MapNone, MapValue, Get, MustGet, ToPointer, Size, and the four predicates IsPresent, IsSome, IsAbsent and IsNone. The README also documents MarshalJSON, UnmarshalJSON and MarshalText, so an Option can cross a JSON boundary. Option implements mo.Foldable[T, U] according to the README, which is the type class defined in fold.go at the root; the typeclass/ directory holds the related abstractions.

The sub-package helpers are the part the README leads with. It shows option.Pipe3 composing a chain, and the repository has a bench/ directory, so the maintainers do measure these paths, though the README does not publish numbers.

## Installing samber/mo and running a first Option chain

The module path is github.com/samber/mo. The README gives the install command with the v1 major version suffix, and states the library follows SemVer strictly with no breaking changes to exported APIs before v2.0.0. The go.mod in the repository declares go 1.18, so a module on an older Go toolchain cannot use it. The README also mentions an AI Agent Skill install line, which is unrelated to the Go dependency and only relevant if you use that tooling.

Install the module into an existing Go module:

```bash
go get github.com/samber/mo@v1
```

After this, the module appears in go.mod and the package is importable. The README shows the import block as the root package:

```go
import (
    "github.com/samber/mo"
)
```

A first real use is the chain the README demonstrates: start with Some, transform with FlatMap, then supply a fallback with OrElse. The README's own example multiplies by 2, takes the remainder modulo 2, adds 21, and comments the result as 21:

```go
option1 := mo.Some(42)

option1.
    FlatMap(func(value int) mo.Option[int] {
        return mo.Some(value * 2)
    }).
    FlatMap(func(value int) mo.Option[int] {
        return mo.Some(value % 2)
    }).
    FlatMap(func(value int) mo.Option[int] {
        return mo.Some(value + 21)
    }).
    OrElse(1234)
// 21
```

To see the None path, the README constructs an empty Option and calls OrElse on it. The comment in the README says the result is 1234, which is the fallback being returned because no value is present:

```go
option2 := mo.None[int]()

option2.OrElse(1234)
// 1234
```

The README also shows Match taking two functions, one for the present case and one for the absent case, and returning a tuple. If you want to compose without method chaining, the README's Pipe3 example in the option sub-package takes the initial value and a sequence of Map and FlatMap steps, and its comment notes the result is None[int] because one step returns mo.None[int]().

## Where samber/mo gets in the way

The first limitation is stylistic and the README does not address it. Go code that returns (T, error) is idiomatic, and mo does not convert that for you. Every boundary between a mo container and a normal function is manual work: you wrap the result, and you unwrap it with Get, MustGet or Match. MustGet panics when the value is absent, which is the opposite of the explicit-error discipline most Go teams adopt. If your codebase is mostly plain Go, introducing Option in one package creates two vocabularies for the same problem.

The second limitation is that the README documents the container types but says little about the concurrency types. Future[T], IO[T], Task[T] and TaskEither[T] appear in the feature list, and the repository has future.go, io.go, io_either.go, task.go and task_either.go with matching test files, but the README excerpt does not explain what runs a Future, whether it is lazy or eager, or how cancellation is expressed. go.mod includes go.uber.org/goleak, which suggests the test suite checks for leaked goroutines, but that is a testing dependency, not a documented runtime contract. Treat these types as unverified until you read the GoDoc page.

The third is the predicate surface. The README lists IsPresent, IsSome, IsAbsent and IsNone as four separate methods on Option without explaining when each is appropriate. Two pairs of synonyms is a small thing, but it means reviewers will see both spellings in the same codebase.

Finally, the README's own note on dot-importing the library is candid: it says the author cannot recommend it and takes no responsibility. That is a fair warning about readability, not a technical defect.

## samber/mo against samber/lo and hand-rolled generics

The closest alternative is not another monad library but the same author's samber/lo, which the README lists under "See also" as a Lodash-style Go library based on Go 1.18+ generics. The difference in approach is concrete. lo operates on slices, maps and channels with free functions such as map and filter; it does not introduce a wrapper type around a single value, so it does not change what a function returns. mo introduces container types that a function must return and a caller must unwrap. If your problem is transforming collections, lo is the right tool and mo is not. If your problem is a function that may or may not produce a value, and you want to chain transformations on that value without branching, mo is the one that addresses it.

The other alternative is writing Option yourself. With Go 1.18 generics, a minimal Some/None type with Map and OrElse is a short file, and you control the method set and the JSON behaviour. The trade-off is that you also own the edge cases: the README documents MarshalJSON, UnmarshalJSON and MarshalText on Option, plus ForEach, MapNone, MapValue and the fold interface, and reproducing that surface correctly is more work than it looks. Choosing mo means accepting its naming decisions, including the four predicates.

The README also points to samber/ro for reactive programming, samber/do for dependency injection, and samber/cc-skills-golang for agent skills. Those are separate concerns; none of them substitutes for mo's container types.

## Maintenance, licence and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-22. The most recent release listed is v1.17.0 on 2026-06-02, preceded by v1.16.0 on 2025-09-25 and v1.15.0 on 2025-08-10. That is a steady but not rapid release rhythm, with roughly a year between v1.15.0 and v1.17.0.

The README makes the upgrade contract explicit: the library is v1 and follows SemVer strictly, and no breaking changes will be made to exported APIs before v2.0.0. For a dependency you import across a large codebase, that is the statement that matters. The practical upgrade cost is therefore low as long as you stay on the v1 module path, which is why the README's install command carries the @v1 suffix. Moving to v2 would be a deliberate migration.

The dependency cost is also stated: the README says the library has no dependencies except the Go standard library. The go.mod confirms this by listing stretchr/testify, go.uber.org/goleak and an indirect yaml module, and by carrying a comment that dependencies are excluded from releases and should be checked in CI. Those are test and tooling dependencies, not runtime ones.

The licence is MIT, per the repository's LICENSE file and the licence badge in the README. MIT is permissive, so it can be used in closed-source products, but this is a description of the licence identifier and not legal advice; if your organisation has approval rules for third-party code, run it through them.

The Makefile shows the maintenance surface a contributor would touch: build, test with the race detector, bench with benchmem over three counts, coverage, golangci-lint, and an audit target that pipes module metadata through nancy. If you fork mo, those are the commands you inherit.

## Conclusion

Adopt samber/mo if you want Option and Result types with Map, FlatMap and Match in Go code that already targets 1.18 or newer, and you accept a std-lib-only dependency. Do not adopt it expecting it to replace Go error handling, or expecting a scheduler behind Future and Task; the README describes types and combinators, not a runtime. Before adding it to a project, read the GoDoc page for the Option methods you plan to use and confirm which of IsPresent, IsSome, IsAbsent and IsNone fits your call sites, because the README lists all four without saying when to prefer one.

## FAQ

### What is samber/mo?

It is a Go library that provides monads and functional programming abstractions, using Go 1.18+ generics. The README lists Option, Result, Either, EitherX, Future, IO, IOEither, Task, TaskEither and State, and states the library has no dependencies except the Go standard library.

### How do I install samber/mo?

The README gives the command go get github.com/samber/mo@v1, and shows importing the root package as github.com/samber/mo. The go.mod declares go 1.18, so the module needs a Go toolchain at that version or newer.

### Does samber/mo have any dependencies?

The README states the library has no dependencies except the Go standard library. The go.mod lists stretchr/testify, go.uber.org/goleak and an indirect yaml module, with a comment that dependencies are excluded from releases and should be checked in CI.

### Is samber/mo actively maintained?

The repository is not archived and the last push was on 2026-09-22. The most recent listed release is v1.17.0 on 2026-06-02, and the README states the library follows SemVer strictly with no breaking changes to exported APIs before v2.0.0.

### What licence does samber/mo use?

The repository carries an MIT licence, shown in the README badge and in the LICENSE file at the root. That is the licence identifier only, not a statement about how it applies to your project.

## Sources

- [License: MIT](https://github.com/samber/mo/blob/master/LICENSE)
- [Project website](https://pkg.go.dev/github.com/samber/mo)
- [README](https://github.com/samber/mo/blob/master/README.md)
- [Releases](https://github.com/samber/mo/releases)
- [samber/mo on GitHub](https://github.com/samber/mo)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/samber-mo
