Description
Futures have been proposed previously in various forms, most notably in #17466 where it was turned down due to being too unimportant to be done as a language change. Since then, generics have been added to the language, making it quite simple to implement futures in a type-safe way without needing any language changes. So here's a proposal to do just that.
Conceptually, a future functions similarly to a one-time use channel that can yield the value that it's given more than once. The benefit over just a normal channel is that it simplifies a 1:M case where multiple receivers need to wait on the same piece of data from a single source. This covers some of the cases, for example, that #16620 is meant to handle.
Here's a possible implementation:
package future
import (
"fmt"
"sync"
"time"
)
type Future[T any] struct {
done chan struct{}
val T
}
func New[T any]() (f *Future[T], complete func(T)) {
var once sync.Once
f = &Future[T]{
done: make(chan struct{}),
}
return f, func(val T) {
once.Do(func() {
f.val = val
close(f.done)
})
}
}
// Unsure on the name. Maybe Ready or something instead?
func (f *Future[T]) Done() <-chan struct{} {
return f.done
}
// Maybe Value instead of Get? Blocking in Value feels weird to me, though.
func (f *Future[T]) Get() T {
<-f.done
return f.val
}
And an example of the usage:
func example() *future.Future[string] {
f, complete := future.New[string]()
go func() {
time.Sleep(3 * time.Second) // Long-running operation of some kind or something.
complete("Voila.")
}()
return f
}
func main() {
r := example()
fmt.Println(r.Get())
}
This API is patterned after context.Context
. The exposure of the done
channel makes it simple to wait for a future in a select
case, but it's not necessary to use it to get the value in a safe way.
If the package was deemed to be useful after sitting in x/exp
, it could be promoted to either x/sync
or directly into sync
.
Metadata
Metadata
Assignees
Type
Projects
Status