Tiny Result Library in Go

Go can sometimes be frustrating—especially when it comes to error handling. Let me be clear: I love how simple and pragmatic Go is. But I also dislike how noisy and repetitive error handling can get. Writing if err != nil { return err } twenty times in a single function is not only boring—it also feels like programming in assembly when I signed up for a higher-level language.

So I wrote a Result[T] type. It’s small, generic, and gives me a taste of the expressive power you get in Rust. Below is how it works and why I think it’s a good (though not perfect) abstraction.

Implementation

package result

import "fmt"

type Result[T any] struct {
	val T
	err error
}

func From[T any](val T, err error) Result[T] {
	if err != nil {
		return Err[T](err)
	}
	return Ok(val)
}

func Ok[T any](val T) Result[T] {
	return Result[T]{val: val, err: nil}
}

func Err[T any](err error) Result[T] {
	var none T
	return Result[T]{val: none, err: err}
}

func (r Result[T]) IsErr() bool {
	return r.err != nil
}

func (r Result[T]) IsOk() bool {
	return r.err == nil
}

func (r Result[T]) Unwrap() T {
	if r.err != nil {
		panic(fmt.Sprintf("called Unwrap on Err: %v", r.err))
	}
	return r.val
}

func (r Result[T]) UnwrapOr(defaultVal T) T {
	if r.err != nil {
		return defaultVal
	}
	return r.val
}

func (r Result[T]) Map(f func(T) T) Result[T] {
	if r.err != nil {
		return r
	}
	return Ok(f(r.val))
}

func (r Result[T]) AndThen(f func(T) Result[T]) Result[T] {
	if r.err != nil {
		return r
	}
	return f(r.val)
}

What’s Good About This?

Cleaner Chaining

Instead of writing verbose error handling like this:

res, err := doThing()
if err != nil {
    return err
}
res2, err := doAnotherThing(res)
if err != nil {
    return err
}

You can express the same logic more concisely:

result.From(doThing()).
    AndThen(doAnotherThing).
    Unwrap()

This approach is more readable and declarative. It lets you focus on the data flow, not on boilerplate.

Safety with Panic Option

The Unwrap() method panics if the result contains an error, which is useful for quick scripts or test scenarios where you don’t want verbose error checks.
If you want a fallback, there’s UnwrapOr().

This duality is inspired by Rust’s Result type.

Generic and Reusable

By leveraging Go’s generics, this works with any type. You get all the benefits of type safety without needing type assertions or reflection tricks.

Functional Flavor

Map() and AndThen() bring in a functional programming style.

This makes it possible to write expressive pipelines without nested conditionals.

Conclusion

I built this Result library not to rewrite Go’s philosophy, but to work with it. It’s a small abstraction that helps me write cleaner, more composable code—especially in projects where performance isn’t critical and developer happiness matters more.

If error handling in Go starts to feel like busywork, give it a try. Just remember: abstractions are tools, not rules. Use them where they help, and avoid them where they don’t.