How the Result Package Represents Tuples for Operation Outcomes in Go

The result package models operation outcomes as generic tuples containing a value and an error using the Result[T] struct, which encapsulates both components in a single type-safe container.

The aperturerobotics/util repository provides a lightweight solution for handling function returns in Go through its result package. This package eliminates the need for multiple return values by representing operation outcomes as structured tuples, making it easier to pass results between functions and store them in collections.

Understanding the Result[T] Struct

At the core of the tuple representation is the generic Result[T] struct defined in result/result.go. This struct stores the successful return value alongside any error that occurred during execution:

type Result[T comparable] struct {
    val T      // the operation's value
    err error  // the operation's error
}

The struct uses Go generics to constrain the value type T to comparable, ensuring that values can be equality-checked. This design allows the package to work with primitive types, strings, and other comparable structs while maintaining compile-time type safety.

Constructing Result Tuples

The NewResult function creates tuple instances in a single call, accepting both the value and error as parameters:

func NewResult[T comparable](val T, err error) *Result[T] {
    return &Result[T]{val: val, err: err}
}

This constructor pattern ensures that both components of the tuple are initialized simultaneously, preventing partial result states. By returning a pointer to the struct, the function enables efficient passing of results without copying the underlying data.

Accessing and Comparing Results

The package provides methods to interact with the tuple components while preserving the value-error relationship. The GetValue method returns both elements in their original order:

func (r *Result[T]) GetValue() (val T, err error) {
    return r.val, r.err
}

For validation purposes, the Compare method checks exact equality between two result tuples:

func (r *Result[T]) Compare(ot *Result[T]) bool {
    return r.val == ot.val && r.err == ot.err
}

This comparison evaluates both the value and error fields, making it useful for unit testing and result caching scenarios.

Practical Implementation Example

The following example demonstrates how to use the result package to represent operation outcomes in a real-world scenario:

package main

import (
    "errors"
    "fmt"

    "github.com/aperturerobotics/util/result"
)

// Example operation that returns an int and an error.
func compute(x int) *result.Result[int] {
    if x < 0 {
        return result.NewResult[int](0, errors.New("negative input"))
    }
    return result.NewResult[int](x*x, nil)
}

func main() {
    r1 := compute(5)
    val, err := r1.GetValue()
    fmt.Printf("Result: %d, Err: %v\n", val, err) // Result: 25, Err: <nil>

    r2 := compute(-3)
    _, err = r2.GetValue()
    fmt.Printf("Error: %v\n", err) // Error: negative input

    // Comparing two results
    r3 := result.NewResult[int](25, nil)
    fmt.Println("Equal?", r1.Compare(r3)) // Equal? true
}

This implementation shows how the tuple representation simplifies error handling by encapsulating both success and failure states in a single returnable object.

Summary

  • The result package uses a generic Result[T] struct to represent operation outcomes as value-error tuples in result/result.go.
  • Construction occurs through NewResult, which accepts both the value and error in a single call.
  • Access methods like GetValue preserve the tuple structure while allowing destructuring into separate variables.
  • Comparison via Compare enables equality checks against other result instances for testing and validation.
  • The generic constraint on T ensures type safety while supporting any comparable Go type.

Frequently Asked Questions

How does the Result type differ from Go's native multiple return values?

While Go functions typically return values and errors as separate entities (val, err), the Result[T] struct encapsulates both into a single object that can be stored in variables, passed to channels, or inserted into collections. This representation treats the outcome as a cohesive unit rather than separate return values, making it ideal for functional programming patterns and asynchronous operations.

What types can be used with the Result generic parameter?

The Result[T] struct constrains T to the comparable interface, meaning any type that supports the == and != operators. This includes primitive types like int, string, and bool, as well as arrays and structs composed of comparable types. Non-comparable types such as slices, maps, and functions cannot be used directly as the value type in a Result.

When should I use Compare versus direct field access?

Use the Compare method when you need to verify that two operations produced identical outcomes, particularly in unit tests or caching scenarios where semantic equality matters. Direct field access through GetValue is preferred when you need to handle the value and error separately with custom logic, such as logging specific error types while processing successful values differently.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →