Skip to content

Commit 1c74443

Browse files
committed
Add OnceFuncReflect alongside Once{Func,Value,Values}
1 parent efdc868 commit 1c74443

File tree

2 files changed

+150
-31
lines changed

2 files changed

+150
-31
lines changed

once.go

+57-31
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,70 @@
11
package sync
22

3-
import "sync"
3+
import (
4+
"fmt"
5+
"reflect"
6+
"sync"
7+
)
48

5-
// OnceFunc returns a function that invokes fn only once and returns the values
6-
// returned by fn. The returned function may be called concurrently.
7-
func OnceFunc[T any](fn func() (T, error)) func() (T, error) {
9+
// OnceFunc returns a function that invokes fn only once.
10+
// The returned function may be called concurrently.
11+
func OnceFunc(fn func()) func() {
12+
var once sync.Once
13+
return func() {
14+
once.Do(fn)
15+
}
16+
}
17+
18+
// OnceValue returns a function that invokes fn only once and returns the
19+
// value returned by fn. The returned function may be called concurrently.
20+
func OnceValue[T any](fn func() T) func() T {
821
var (
9-
once sync.Once
10-
value T
11-
err error
22+
once sync.Once
23+
v T
1224
)
13-
return func() (T, error) {
25+
return func() T {
1426
once.Do(func() {
15-
value, err = fn()
27+
v = fn()
1628
})
17-
return value, err
29+
return v
1830
}
1931
}
2032

21-
type OnceValue[T any] struct {
22-
once sync.Once
23-
v T
24-
}
25-
26-
func (o *OnceValue[T]) Do(fn func() T) T {
27-
o.once.Do(func() {
28-
o.v = fn()
29-
})
30-
return o.v
31-
}
32-
33-
type OnceValueErr[T any] struct {
34-
once sync.Once
35-
v T
36-
err error
33+
// OnceValues returns a function that invokes fn only once and returns the
34+
// values returned by fn. The returned function may be called concurrently.
35+
func OnceValues[T1, T2 any](fn func() (T1, T2)) func() (T1, T2) {
36+
var (
37+
once sync.Once
38+
v1 T1
39+
v2 T2
40+
)
41+
return func() (T1, T2) {
42+
once.Do(func() {
43+
v1, v2 = fn()
44+
})
45+
return v1, v2
46+
}
3747
}
3848

39-
func (o *OnceValueErr[T]) Do(fn func() (T, error)) (T, error) {
40-
o.once.Do(func() {
41-
o.v, o.err = fn()
42-
})
43-
return o.v, o.err
49+
// OnceFuncReflect returns a function that invokes fn only once and returns the
50+
// value returned by fn. The returned function may be called concurrently.
51+
func OnceFuncReflect[F any](fn F) F {
52+
fnv := reflect.ValueOf(fn)
53+
if fnv.Kind() != reflect.Func {
54+
panic(fmt.Errorf("sync: OnceFuncReflect called with non-function value (%T)", fn))
55+
}
56+
fnt := fnv.Type()
57+
if fnt.NumIn() != 0 {
58+
panic(fmt.Errorf("sync: OnceFuncReflect called with function with more than zero args (%T)", fn))
59+
}
60+
var (
61+
once sync.Once
62+
results []reflect.Value
63+
)
64+
return reflect.MakeFunc(fnt, func([]reflect.Value) []reflect.Value {
65+
once.Do(func() {
66+
results = fnv.Call(nil)
67+
})
68+
return results
69+
}).Interface().(F)
4470
}

once_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package sync
2+
3+
import (
4+
"errors"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
var oops = errors.New("oops")
10+
11+
func TestOnceFuncReflect(t *testing.T) {
12+
var calls int
13+
check := func() {
14+
if calls > 0 {
15+
panic("function called more than once")
16+
}
17+
calls++
18+
}
19+
for _, c := range []struct {
20+
f any
21+
want []any
22+
}{
23+
{func() { check() }, nil},
24+
{func() int { check(); return 42 }, []any{42}},
25+
{func() (int, error) { check(); return 42, oops }, []any{42, oops}},
26+
} {
27+
t.Run(reflect.TypeOf(c.f).String(), func(t *testing.T) {
28+
calls = 0
29+
fn := OnceFuncReflect(c.f)
30+
for j := 0; j < 2; j++ {
31+
got := reflect.ValueOf(fn).Call(nil)
32+
if len(got) != len(c.want) {
33+
t.Fatalf("got %d results, want %d", len(got), len(c.want))
34+
}
35+
for i, v := range got {
36+
if g, w := v.Interface(), c.want[i]; g != w {
37+
t.Fatalf("result %d is %v, want %v", i, g, w)
38+
}
39+
}
40+
}
41+
})
42+
}
43+
}
44+
45+
func BenchmarkOnceFunc(b *testing.B) {
46+
b.Run("Func", func(b *testing.B) {
47+
fn := OnceFunc(func() {})
48+
for i := 0; i < b.N; i++ {
49+
fn()
50+
}
51+
})
52+
b.Run("Value", func(b *testing.B) {
53+
fn := OnceValue(func() int { return 42 })
54+
for i := 0; i < b.N; i++ {
55+
if v := fn(); v != 42 {
56+
b.Fatalf("got %v, want 42", v)
57+
}
58+
}
59+
})
60+
b.Run("Values", func(b *testing.B) {
61+
fn := OnceValues(func() (int, error) { return 42, oops })
62+
for i := 0; i < b.N; i++ {
63+
if v, err := fn(); v != 42 || err != oops {
64+
b.Fatalf("got %v, %v, want 42, oops", v, err)
65+
}
66+
}
67+
})
68+
}
69+
70+
func BenchmarkOnceFuncReflect(b *testing.B) {
71+
b.Run("Func", func(b *testing.B) {
72+
fn := OnceFuncReflect(func() {})
73+
for i := 0; i < b.N; i++ {
74+
fn()
75+
}
76+
})
77+
b.Run("Value", func(b *testing.B) {
78+
fn := OnceFuncReflect(func() int { return 42 })
79+
for i := 0; i < b.N; i++ {
80+
if v := fn(); v != 42 {
81+
b.Fatalf("got %v, want 42", v)
82+
}
83+
}
84+
})
85+
b.Run("Values", func(b *testing.B) {
86+
fn := OnceFuncReflect(func() (int, error) { return 42, oops })
87+
for i := 0; i < b.N; i++ {
88+
if v, err := fn(); v != 42 || err != oops {
89+
b.Fatalf("got %v, %v, want 42, oops", v, err)
90+
}
91+
}
92+
})
93+
}

0 commit comments

Comments
 (0)