An (Idiomatic?) Go Future Implementation

The future pattern is almost ubiquitous in software development, but you don't often see it in Go. I think there are a number of good reasons for this  – one being the lack of support in the standard library or any good community supported packages.

Flawed Approaches

Most articles and packages dealing with futures in Go treat chan interface{} as a future. While this seems like the idiomatic approach, it has a couple of major flaws.

  • It requires the use of interface{} or code generation to write a general purpose implementation.
  • A value can only be received from chan interface{} once. Extra code must be written if the future needs to be used by multiple downstream actors.
  • It is easy to accidentally receive the zero value after the channel has been closed.

An (Idiomatic?) Implementation

Taking inspiration from the context package, closing a chan struct{} can be used to signal when a Future has resolved.

// Future is a synchronization primitive that guards data that may be 
// available in the future.
type Future interface {
	// Done returns a channel that is closed when the Future is
	// resolved. It is safe to call Done multiple times across 
	// multiple threads.
	Done() <-chan struct{}
}

This approach differs from most future implementation by not acting as a container for data, but as a guard for data that will be available in the future.

// Define a value that will be available in the future.
var val string
fut, resolve := async.NewFuture()

// Simulate long computation or IO by sleeping before setting the value and resolving
// the future.
go func() {
	time.Sleep(500 * time.Millisecond)
	val = "Hello World!"
	resolve()
}()

// Block until the future is resolved.
<- fut.Done()

fmt.Println(val)

In practice a Future can be embedded into a container to more closely match the traditional behavior of a future.

type Container struct {
	async.Future
	Value interface{}
	Err   error
}

func NewContainer() (*Container, func(interface{}, error)) {
	fut, resolve := async.NewFuture()
	container := &Container{
		Future: fut,
	}

	fn := func(value interface{}, err error) {
		container.Value = value
		container.Err = err
		resolve()
	}

	return container, fn
}

func Example() {
	// Define a value that will be available in the future.
	container, resolve := NewContainer()

	// Simulate long computation or IO by sleeping before
	// resolving the future.
	go func() {
		time.Sleep(500 * time.Millisecond)
		resolve("Hello World!", nil)
	}()

	// Block until the future is resolved.
	<-container.Done()

	fmt.Println(container.Value, container.Err)
	// output: Hello World! <nil>
}

A full implementation using this technique can be found in github.com/ianlopshire/go-async. Please feel free to open an issue if you have any thoughts or feedback.