Description
This is a proposal for adding a generic OnceFunc
function to the sync
package in the standard library.
In my team's codebase we recently added this function to our private syncutil
package:
// OnceFunc returns a function that invokes fn only once and returns the values
// returned by fn. The returned function may be called concurrently.
func OnceFunc[T any](fn func() (T, error)) func() (T, error)
(I put this in the (temporary) module github.com/adg/sync
, if you want to try it out.)
This makes a common use of sync.Once
, lazy initialization with error handling, more ergonomic.
For example, this Server
struct that wants to lazily initialize its database connection may use sync.Once
:
type Server struct {
dbPath string
dbInit sync.Once
dbVal *sql.DB
dbErr error
}
func NewServer(dbPath string) *Server {
return &Server{
dbPath: dbPath,
}
}
func (s *Server) db() (*sql.DB, error) {
s.dbInit.Do(func() {
s.dbVal, s.dbErr = sql.Open("sqlite", s.dbPath)
})
return s.dbVal, s.dbErr
}
func (s *Server) DoSomething() error {
db, err := s.db()
if err != nil {
return err
}
_ = db // do something with db
return nil
}
While with OnceFunc
a lot of the fuss goes away:
type Server struct {
db func() (*sql.DB, error)
}
func NewServer(dbPath string) *Server {
return &Server{
db: sync.OnceFunc(func() (*sql.DB, error) {
return sql.Open("sqlite", dbPath)
}),
}
}
func (s *Server) DoSomething() error {
db, err := s.db()
if err != nil {
return err
}
_ = db // do something with db
return nil
}
Playground links: before and after.
If there is interest in this, then I suppose it should first live in x/exp
(as with the slices
and maps
packages) so that we can play with it.
This seems to me like a great example of how generics can be used in the standard library. I wasn't able to find an overall tracking bug for putting generics in the standard library, otherwise I'd have referenced it here.