Skip to content

sync: add OnceFunc, OnceValue, OnceValues #56102

Closed
@adg

Description

@adg

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions