@ -4,7 +4,16 @@
// Package lazy provides types for lazily initialized values.
// Package lazy provides types for lazily initialized values.
package lazy
package lazy
import "sync"
import (
"sync"
"sync/atomic"
"tailscale.com/types/ptr"
)
// nilErrPtr is a sentinel *error value for SyncValue.err to signal
// that SyncValue.v is valid.
var nilErrPtr = ptr . To [ error ] ( nil )
// SyncValue is a lazily computed value.
// SyncValue is a lazily computed value.
//
//
@ -17,7 +26,17 @@ import "sync"
type SyncValue [ T any ] struct {
type SyncValue [ T any ] struct {
once sync . Once
once sync . Once
v T
v T
err error
// err is either:
// * nil, if not yet computed
// * nilErrPtr, if completed and nil
// * non-nil and not nilErrPtr on error.
//
// It is an atomic.Pointer so it can be read outside of the sync.Once.Do.
//
// Writes to err must happen after a write to v so a caller seeing a non-nil
// err can safely read v.
err atomic . Pointer [ error ]
}
}
// Set attempts to set z's value to val, and reports whether it succeeded.
// Set attempts to set z's value to val, and reports whether it succeeded.
@ -26,6 +45,7 @@ func (z *SyncValue[T]) Set(val T) bool {
var wasSet bool
var wasSet bool
z . once . Do ( func ( ) {
z . once . Do ( func ( ) {
z . v = val
z . v = val
z . err . Store ( nilErrPtr ) // after write to z.v; see docs
wasSet = true
wasSet = true
} )
} )
return wasSet
return wasSet
@ -41,15 +61,63 @@ func (z *SyncValue[T]) MustSet(val T) {
// Get returns z's value, calling fill to compute it if necessary.
// Get returns z's value, calling fill to compute it if necessary.
// f is called at most once.
// f is called at most once.
func ( z * SyncValue [ T ] ) Get ( fill func ( ) T ) T {
func ( z * SyncValue [ T ] ) Get ( fill func ( ) T ) T {
z . once . Do ( func ( ) { z . v = fill ( ) } )
z . once . Do ( func ( ) {
z . v = fill ( )
z . err . Store ( nilErrPtr ) // after write to z.v; see docs
} )
return z . v
return z . v
}
}
// GetErr returns z's value, calling fill to compute it if necessary.
// GetErr returns z's value, calling fill to compute it if necessary.
// f is called at most once, and z remembers both of fill's outputs.
// f is called at most once, and z remembers both of fill's outputs.
func ( z * SyncValue [ T ] ) GetErr ( fill func ( ) ( T , error ) ) ( T , error ) {
func ( z * SyncValue [ T ] ) GetErr ( fill func ( ) ( T , error ) ) ( T , error ) {
z . once . Do ( func ( ) { z . v , z . err = fill ( ) } )
z . once . Do ( func ( ) {
return z . v , z . err
var err error
z . v , err = fill ( )
// Update z.err after z.v; see field docs.
if err != nil {
z . err . Store ( ptr . To ( err ) )
} else {
z . err . Store ( nilErrPtr )
}
} )
return z . v , * z . err . Load ( )
}
// Peek returns z's value and a boolean indicating whether the value has been
// set successfully. If a value has not been set, the zero value of T is
// returned.
//
// This function is safe to call concurrently with Get/GetErr/Set, but it's
// undefined whether a value set by a concurrent call will be visible to Peek.
//
// To get any error that's been set, use PeekErr.
//
// If GetErr's fill function returned a valid T and an non-nil error, Peek
// discards that valid T value. PeekErr returns both.
func ( z * SyncValue [ T ] ) Peek ( ) ( v T , ok bool ) {
if z . err . Load ( ) == nilErrPtr {
return z . v , true
}
var zero T
return zero , false
}
// PeekErr returns z's value and error and a boolean indicating whether the
// value or error has been set. If ok is false, T and err are the zero value.
//
// This function is safe to call concurrently with Get/GetErr/Set, but it's
// undefined whether a value set by a concurrent call will be visible to Peek.
//
// Unlike Peek, PeekErr reports ok if either v or err has been set, not just v,
// and returns both the T and err returned by GetErr's fill function.
func ( z * SyncValue [ T ] ) PeekErr ( ) ( v T , err error , ok bool ) {
if e := z . err . Load ( ) ; e != nil {
return z . v , * e , true
}
var zero T
return zero , nil , false
}
}
// SyncFunc wraps a function to make it lazy.
// SyncFunc wraps a function to make it lazy.