// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sync // OnceFunc returns a function that invokes f only once. The returned function // may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceFunc(f func()) func() { // Use a struct so that there's a single heap allocation. d := struct { f func() once Once valid bool p any }{ f: f, } return func() { d.once.Do(func() { defer func() { d.f = nil // Do not keep f alive after invoking it. d.p = recover() if !d.valid { // Re-panic immediately so on the first // call the user gets a complete stack // trace into f. panic(d.p) } }() d.f() d.valid = true // Set only if f does not panic. }) if !d.valid { panic(d.p) } } } // OnceValue returns a function that invokes f only once and returns the value // returned by f. The returned function may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceValue[T any](f func() T) func() T { // Use a struct so that there's a single heap allocation. d := struct { f func() T once Once valid bool p any result T }{ f: f, } return func() T { d.once.Do(func() { defer func() { d.f = nil d.p = recover() if !d.valid { panic(d.p) } }() d.result = d.f() d.valid = true }) if !d.valid { panic(d.p) } return d.result } } // OnceValues returns a function that invokes f only once and returns the values // returned by f. The returned function may be called concurrently. // // If f panics, the returned function will panic with the same value on every call. func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { // Use a struct so that there's a single heap allocation. d := struct { f func() (T1, T2) once Once valid bool p any r1 T1 r2 T2 }{ f: f, } return func() (T1, T2) { d.once.Do(func() { defer func() { d.f = nil d.p = recover() if !d.valid { panic(d.p) } }() d.r1, d.r2 = d.f() d.valid = true }) if !d.valid { panic(d.p) } return d.r1, d.r2 } }