├── .gitignore ├── LICENSE ├── README.md ├── builtin.go ├── builtin_test.go ├── byteseq.go ├── byteseq_test.go ├── conv.go ├── conv_test.go ├── debug.go ├── errors.go ├── errors_test.go ├── fmt.go ├── go.mod ├── io.go ├── io_test.go ├── maps.go ├── maps_test.go ├── nstd.go ├── numeric.go ├── numeric_test.go ├── ordered.go ├── ordered_test.go ├── os.go ├── reflect.go ├── reflect_test.go ├── slices.go ├── slices_test.go ├── sync.go ├── sync_test.go └── time.go /.gitignore: -------------------------------------------------------------------------------- 1 | gen-doc-with-std.sh 2 | go101-docs 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TapirLiu & Go101.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **nstd** is a single-package go module which provides some missing types and functions in the standard library. 2 | 3 | Examples: https://go101.org/apps-and-libs/nstd.html 4 | 5 | Docs: https://docs.go101.org/std/pkg/go101.org/nstd.html 6 | 7 | -------------------------------------------------------------------------------- /builtin.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type blank = struct{} // used internally 8 | 9 | // Generally, Panicf(format, v...) is a short form of panic(fmt.Sprintf(format, v...)). 10 | // When format is blank, then it is a short form of panic(fmt.Sprint(v...)). 11 | func Panicf(format string, a ...any) bool { 12 | if format == "" { 13 | panic(fmt.Sprint(a...)) 14 | } else { 15 | panic(fmt.Sprintf(format, a...)) 16 | } 17 | return true 18 | } 19 | 20 | // Must panics if err is not nil; otherwise, the T value is returned. 21 | // 22 | // The function is mianly to support chain calls like Must(...).MethodOfT(...). 23 | // 24 | // See: https://github.com/golang/go/issues/58280 25 | func Must[T any](v T, err error) T { 26 | if err != nil { 27 | panic(err) 28 | } 29 | return v 30 | } 31 | 32 | // Eval is used to ensure the evaluation order of some expressions 33 | // in a statement. 34 | // 35 | // See: 36 | // 37 | // * https://go101.org/article/evaluation-orders.html 38 | // * https://github.com/golang/go/issues/27804 39 | // * https://github.com/golang/go/issues/36449 40 | func Eval[T any](v T) T { 41 | return v 42 | } 43 | 44 | // ZeroOf[T]() and ZeroOf(valueOfT) both return the zero value of type T. 45 | func ZeroOf[T any](T) T { 46 | var x T 47 | return x 48 | } 49 | 50 | // Zero(p) zeros the value referenced by the pointer p. 51 | // Zero is useful for resetting values of some unexported types, 52 | // or resetting values of some other packages but without importing those packages. 53 | func Zero[T any](p *T) { 54 | var x T 55 | *p = x 56 | } 57 | 58 | // New allocates a T value and initialize it with the specified one. 59 | func New[T any](v T) *T { 60 | return &v 61 | } 62 | 63 | // SliceFrom is used to create a slice from some values of the same type. 64 | // Some use scenarios: 65 | // 1. Convert multiple results of a function call to a []any slice, 66 | // then use the slice in fmt.Printf alike functions. 67 | // 2. Construct a []T slice from some T values without using the []T{...} form. 68 | // 69 | // NOTE: SliceFrom(aSlice...) returns aSlice, 70 | // 71 | // See: https://github.com/golang/go/issues/61213 72 | func SliceFrom[T any](vs ...T) []T { 73 | return vs 74 | } 75 | 76 | // TypeAssert asserts an interface value x to type T. 77 | // If the assertion succeeds, true is returned, othewise, false is returned. 78 | // If into is not nil, then the concrete value of x will be assigned to 79 | // the value referenced by into. 80 | // 81 | // See: https://github.com/golang/go/issues/65846 82 | func TypeAssert[T any](x any, into *T) (ok bool) { 83 | if into != nil { 84 | *into, ok = x.(T) 85 | } else { 86 | _, ok = x.(T) 87 | } 88 | return 89 | } 90 | 91 | // HasMapEntry checks whether or not a map contains an entry 92 | // with the specified key. 93 | func HasEntry[K comparable, E any](m map[K]E, key K) bool { 94 | _, ok := m[key] 95 | return ok 96 | } 97 | -------------------------------------------------------------------------------- /builtin_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestZeroOf(t *testing.T) { 9 | testZeroOf(1, 0, t) 10 | testZeroOf("go", "", t) 11 | testZeroOf(true, false, t) 12 | testZeroOf(struct{}{}, struct{}{}, t) 13 | testZeroOf([2]byte{1, 2}, [2]byte{}, t) 14 | testZeroOf([]byte{1, 2}, nil, t) 15 | } 16 | 17 | func testZeroOf[T any](v, zero T, t *testing.T) { 18 | if z := ZeroOf(v); !reflect.DeepEqual(z, zero) { 19 | t.Fatalf("Zero(%v) != %v (but %v)", v, z, zero) 20 | } 21 | } 22 | 23 | func TestZero(t *testing.T) { 24 | testZero(1, 0, t) 25 | testZero("go", "", t) 26 | testZero(true, false, t) 27 | testZero(struct{}{}, struct{}{}, t) 28 | testZero([2]byte{1, 2}, [2]byte{}, t) 29 | testZero([]byte{1, 2}, nil, t) 30 | } 31 | 32 | func testZero[T any](v, zero T, t *testing.T) { 33 | var old = v 34 | var p = &v 35 | if Zero(p); !reflect.DeepEqual(v, zero) { 36 | t.Fatalf("ZeroIt(&%v) != %v (but %v)", old, v, zero) 37 | } 38 | } 39 | 40 | func TestNew(t *testing.T) { 41 | testNew(1, t) 42 | testNew("go", t) 43 | testNew(true, t) 44 | testNew(struct{}{}, t) 45 | testNew([2]byte{1, 2}, t) 46 | testNew([]byte{1, 2}, t) 47 | } 48 | 49 | func testNew[T any](v T, t *testing.T) { 50 | var pv = &v 51 | if p := New(v); p == pv { 52 | t.Fatalf("New(&%v) == &%v", v, v) 53 | } else if !reflect.DeepEqual(*p, *pv) { 54 | t.Fatalf("*New(&%v) != %v (but %v)", v, v, *p) 55 | } 56 | } 57 | 58 | func TestTypeAssert(t *testing.T) { 59 | testTypeAssert(1, (*int)(nil), true, t) 60 | testTypeAssert(1, New(123), true, t) 61 | testTypeAssert(1, New(true), false, t) 62 | testTypeAssert(true, New(true), true, t) 63 | testTypeAssert(1, New(true), false, t) 64 | testTypeAssert(true, New(any(0)), true, t) 65 | } 66 | 67 | func testTypeAssert[T any](v any, p *T, shouldOkay bool, t *testing.T) { 68 | if TypeAssert(v, p) != shouldOkay { 69 | t.Fatalf("*TypeAssert[%T](%v, %T) != %v", *p, v, p, shouldOkay) 70 | } else if shouldOkay && p != nil && !reflect.DeepEqual(v, *p) { 71 | t.Fatalf("*TypeAssert[%T](%v, %T) fails. (got %v)", *p, v, p, *p) 72 | } 73 | } 74 | 75 | func TestHasEntry(t *testing.T) { 76 | testHasEntry(map[int]int{1: 2}, 1, true, t) 77 | testHasEntry(map[int]int{1: 2}, 2, false, t) 78 | testHasEntry(map[bool]int{true: 2}, true, true, t) 79 | testHasEntry(map[bool]int{false: 2}, true, false, t) 80 | } 81 | 82 | func testHasEntry[K comparable, E any](m map[K]E, key K, shouldOkay bool, t *testing.T) { 83 | if HasEntry(m, key) != shouldOkay { 84 | t.Fatalf("*HasEntry(%v, %v) != %v", m, key, shouldOkay) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /byteseq.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "unicode/utf8" 5 | ) 6 | 7 | // A ByteSeq is either a string or a byte slice. 8 | type ByteSeq interface{ ~string | ~[]byte } 9 | 10 | // ByteSeqCommonPrefixes returns the common prefixes of two [ByteSeq] values. 11 | func ByteSeqCommonPrefix[X, Y ByteSeq](x X, y Y) (x2 X, y2 Y) { 12 | // Tried several coding mamners and found 13 | // the current one is inline-able, with cost 80. 14 | min := minOfTwo(len(x), len(y)) 15 | x2, y2 = x[:min], y[:min] 16 | if len(x2) == len(y2) { // BCE hint 17 | for i := 0; i < len(x2); i++ { 18 | if x2[i] != y2[i] { 19 | return x2[:i], y2[:i] 20 | } 21 | } 22 | } 23 | return 24 | } 25 | 26 | var ( 27 | _, _ = ByteSeqCommonPrefix("", []byte{}) 28 | _, _ = ByteSeqCommonPrefix("", "") 29 | _, _ = ByteSeqCommonPrefix([]byte{}, "") 30 | _, _ = ByteSeqCommonPrefix([]byte{}, []byte{}) 31 | ) 32 | 33 | // ReverseBytes inverts the bytes in a byte slice. 34 | // The argument is returned so that calls to ReverseBytes can be used as expressions. 35 | func ReverseBytes[Bytes ~[]byte](s Bytes) Bytes { 36 | if len(s) == 0 { 37 | return s[:0] 38 | } 39 | var i, j = len(s) - 1, 0 40 | for i > j { 41 | s[i], s[j] = s[j], s[i] 42 | j++ 43 | i-- 44 | } 45 | return s 46 | } 47 | 48 | // ReverseByteSeq returnes a copy (in the form of byte slice) of 49 | // a byte sequence (in the form of either string or byte slice) but reversed. 50 | func ReverseByteSeq[Seq ByteSeq](s Seq) []byte { 51 | if len(s) == 0 { 52 | return []byte(s[:0]) 53 | } 54 | var into = make([]byte, len(s)) 55 | var j = 0 56 | for i := len(s) - 1; i >= 0; i-- { 57 | into[j] = s[i] 58 | j++ 59 | } 60 | return into 61 | } 62 | 63 | // ReverseRuneSeq returnes a copy (in the form of byte slice) of 64 | // a rune sequence (in the form of either string or byte slice) but reversed. 65 | // 66 | // See: 67 | // 68 | // * https://github.com/golang/go/issues/14777 69 | // * https://github.com/golang/go/issues/68348 70 | func ReverseRuneSeq[Seq ByteSeq](s Seq) []byte { 71 | if len(s) == 0 { 72 | return []byte(s[:0]) 73 | } 74 | 75 | var into = make([]byte, len(s)) 76 | var bytes = []byte(s) // doesn't allocate since Go toolchain 1.22 77 | var i = len(bytes) 78 | for { 79 | _, size := utf8.DecodeRune(bytes) 80 | if size == 0 { 81 | if i != 0 { 82 | Panicf("i (%v) != 0", i) 83 | } 84 | break 85 | } 86 | if i < size { 87 | Panicf("i (%v) < size (%v)", i, size) 88 | } 89 | i -= size 90 | var j, k = i, 0 91 | for k < size { 92 | into[j] = bytes[k] 93 | j++ 94 | k++ 95 | } 96 | bytes = bytes[size:] 97 | } 98 | 99 | return into 100 | } 101 | -------------------------------------------------------------------------------- /byteseq_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestByteSeqCommonPrefix(t *testing.T) { 9 | testByteSeqCommonPrefix_4_cases("abcNNN", "abcMMM", 3, t) 10 | testByteSeqCommonPrefix_4_cases("abc", "ab", 2, t) 11 | testByteSeqCommonPrefix_4_cases("abc", "abc", 3, t) 12 | testByteSeqCommonPrefix_4_cases("xyz", "abc", 0, t) 13 | testByteSeqCommonPrefix_4_cases("", "abc", 0, t) 14 | testByteSeqCommonPrefix_4_cases("", "", 0, t) 15 | } 16 | 17 | func testByteSeqCommonPrefix_4_cases(x, y string, expectedLen int, t *testing.T) { 18 | testByteSeqCommonPrefix(string(x), string(y), expectedLen, t) 19 | testByteSeqCommonPrefix(string(x), []byte(y), expectedLen, t) 20 | testByteSeqCommonPrefix([]byte(x), string(y), expectedLen, t) 21 | testByteSeqCommonPrefix([]byte(x), []byte(y), expectedLen, t) 22 | } 23 | 24 | func hasPrefix[T ByteSeq](x, x2 T) bool { 25 | return bytes.HasPrefix([]byte(x), []byte(x2)) 26 | } 27 | 28 | func testByteSeqCommonPrefix[X, Y ByteSeq](x X, y Y, expectedLen int, t *testing.T) { 29 | x2, y2 := ByteSeqCommonPrefix(x, y) 30 | if len(x2) != expectedLen { 31 | t.Fatalf("ByteSeqCommonPrefix(%T(%s), %T(%s)). Expected length (%v) != len(%s)", x, x, y, y, expectedLen, x2) 32 | } 33 | if len(y2) != expectedLen { 34 | t.Fatalf("ByteSeqCommonPrefix(%T(%s), %T(%s)). Expected length (%v) != len(%s)", x, x, y, y, expectedLen, y2) 35 | } 36 | if !hasPrefix(x, x2) { 37 | t.Fatalf("ByteSeqCommonPrefix(%T(%s), %T(%s)). %s is not prefix of %s", x, x, y, y, x2, x) 38 | } 39 | if !hasPrefix(y, y2) { 40 | t.Fatalf("ByteSeqCommonPrefix(%T(%s), %T(%s)). %s is not prefix of %s", x, x, y, y, y2, y) 41 | } 42 | if string(x2) != string(y2) { 43 | t.Fatalf("ByteSeqCommonPrefix(%T(%s), %T(%s)). %s != %s", x, x, y, y, x2, y2) 44 | } 45 | } 46 | 47 | func TestReverseBytes(t *testing.T) { 48 | testReverseBytes("", "", t) 49 | testReverseBytes("a", "a", t) 50 | testReverseBytes("你好!", []byte{0x81, 0xbc, 0xef, 0xbd, 0xa5, 0xe5, 0xa0, 0xbd, 0xe4}, t) 51 | testReverseBytes("abc", "cba", t) 52 | testReverseBytes("abc你好!", []byte{0x81, 0xbc, 0xef, 0xbd, 0xa5, 0xe5, 0xa0, 0xbd, 0xe4, 'c', 'b', 'a'}, t) 53 | testReverseBytes("你好!abc", []byte{'c', 'b', 'a', 0x81, 0xbc, 0xef, 0xbd, 0xa5, 0xe5, 0xa0, 0xbd, 0xe4}, t) 54 | } 55 | 56 | func testReverseBytes[SeqX, SeqY ByteSeq](sx SeqX, sy SeqY, t *testing.T) { 57 | x, y := []byte(sx), []byte(sy) 58 | if string(ReverseBytes(x)) != string(sy) { 59 | t.Fatalf("ReverseBytes(%s) != %s", x, sy) 60 | } 61 | if string(ReverseBytes(y)) != string(sx) { 62 | t.Fatalf("ReverseBytes(%s) != %s", y, sx) 63 | } 64 | if str := string(y); string(ReverseBytes(ReverseBytes(y))) != str { 65 | t.Fatalf("ReverseBytes(ReverseBytes((%s)) != %s", y, str) 66 | } 67 | if str := string(x); string(ReverseBytes(ReverseBytes(x))) != str { 68 | t.Fatalf("ReverseBytes(ReverseBytes((%s)) != %s", x, str) 69 | } 70 | } 71 | 72 | func TestReverseByteSeq(t *testing.T) { 73 | testReverseByteSeq("", "", t) 74 | testReverseByteSeq("a", "a", t) 75 | testReverseByteSeq("你好!", []byte{0x81, 0xbc, 0xef, 0xbd, 0xa5, 0xe5, 0xa0, 0xbd, 0xe4}, t) 76 | testReverseByteSeq("abc", "cba", t) 77 | testReverseByteSeq("abc你好!", []byte{0x81, 0xbc, 0xef, 0xbd, 0xa5, 0xe5, 0xa0, 0xbd, 0xe4, 'c', 'b', 'a'}, t) 78 | testReverseByteSeq("你好!abc", []byte{'c', 'b', 'a', 0x81, 0xbc, 0xef, 0xbd, 0xa5, 0xe5, 0xa0, 0xbd, 0xe4}, t) 79 | } 80 | 81 | func testReverseByteSeq[SeqX, SeqY ByteSeq](sx SeqX, sy SeqY, t *testing.T) { 82 | x, y := []byte(sx), []byte(sy) 83 | if string(ReverseByteSeq(x)) != string(sy) { 84 | t.Fatalf("ReverseByteSeq(%s) != %s", x, sy) 85 | } 86 | if string(ReverseByteSeq(y)) != string(sx) { 87 | t.Fatalf("ReverseByteSeq(%s) != %s", y, sx) 88 | } 89 | if str := string(y); string(ReverseByteSeq(ReverseByteSeq(y))) != str { 90 | t.Fatalf("ReverseByteSeq(ReverseByteSeq((%s)) != %s", y, str) 91 | } 92 | if str := string(x); string(ReverseByteSeq(ReverseByteSeq(x))) != str { 93 | t.Fatalf("ReverseByteSeq(ReverseByteSeq((%s)) != %s", x, str) 94 | } 95 | } 96 | 97 | func TestReverseRuneSeq(t *testing.T) { 98 | testReverseRuneSeq("", "", t) 99 | testReverseRuneSeq("a", "a", t) 100 | testReverseRuneSeq("你好!", "!好你", t) 101 | testReverseRuneSeq("abc", "cba", t) 102 | testReverseRuneSeq("abc你好!", "!好你cba", t) 103 | testReverseRuneSeq("你好!abc", "cba!好你", t) 104 | } 105 | 106 | func testReverseRuneSeq[SeqX, SeqY ByteSeq](sx SeqX, sy SeqY, t *testing.T) { 107 | x, y := []byte(sx), []byte(sy) 108 | if string(ReverseRuneSeq(x)) != string(sy) { 109 | t.Fatalf("ReverseRuneSeq(%s) != %s", x, sy) 110 | } 111 | if string(ReverseRuneSeq(y)) != string(sx) { 112 | t.Fatalf("ReverseRuneSeq(%s) != %s", y, sx) 113 | } 114 | if str := string(y); string(ReverseRuneSeq(ReverseRuneSeq(y))) != str { 115 | t.Fatalf("ReverseRuneSeq(ReverseRuneSeq((%s)) != %s", y, str) 116 | } 117 | if str := string(x); string(ReverseRuneSeq(ReverseRuneSeq(x))) != str { 118 | t.Fatalf("ReverseRuneSeq(ReverseRuneSeq((%s)) != %s", x, str) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /conv.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | // Btoi converts a bool value to int (true -> 1, false -> 0). 4 | // 5 | // See: https://github.com/golang/go/issues/64825 6 | func Btoi[B ~bool](x B) int { 7 | if x { 8 | return 1 9 | } 10 | return 0 11 | } 12 | -------------------------------------------------------------------------------- /conv_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBtoi(t *testing.T) { 8 | if Btoi(true) != 1 { 9 | t.Fatal("Btoi(true) should be 1") 10 | } 11 | if Btoi(false) != 0 { 12 | t.Fatal("Btoi(true) should be 0") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | var debugLogger = log.New(os.Stderr, "[debug]: ", 0) 9 | 10 | // Debug calls [log.Print] and returns true, 11 | // so that it can be used in 12 | // 13 | // _ = DebugMode && nstd.Debug(...) 14 | func Debug(v ...any) bool { 15 | debugLogger.Print(v...) 16 | return true 17 | } 18 | 19 | // Debugf calls [log.Printf] and return true, 20 | // so that it can be used in 21 | // 22 | // _ = DebugMode && nstd.Debugf(...) 23 | func Debugf(format string, v ...any) bool { 24 | debugLogger.Printf(format, v...) 25 | return true 26 | } 27 | 28 | // assert is used interanlly 29 | func assert(condition bool, failMessage string, args ...any) bool { 30 | if !condition { 31 | if len(args) == 0 { 32 | debugLogger.Print(failMessage) 33 | } else { 34 | debugLogger.Printf(failMessage, args...) 35 | } 36 | panic("Assert fails") 37 | } 38 | 39 | return true 40 | } 41 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "unique" 5 | ) 6 | 7 | // Error returns an error that formats as the given text. 8 | // Different from errors.New in the std library, two calls 9 | // to Error return an identical error value if the texts are identical. 10 | func Error(text string) error { 11 | return errorString{unique.Make(text)} 12 | } 13 | 14 | type errorString struct { 15 | s unique.Handle[string] 16 | } 17 | 18 | func (e errorString) Error() string { 19 | return e.s.Value() 20 | } 21 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestNewError(t *testing.T) { 9 | var base = strings.Repeat("abc", 3) 10 | var e1 = Error(base[0:3]) 11 | var e2 = Error(base[3:6]) 12 | var e3 = Error(base[6:9]) 13 | if e1 != e2 { 14 | t.Fatal("NewError: e1 != e2") 15 | } 16 | if e1 != e3 { 17 | t.Fatal("NewError: e1 != e3") 18 | } 19 | if e3 != e2 { 20 | t.Fatal("NewError: e3 != e2") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fmt.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Printfln(format, a...) is a combintion of fmt.Printf(format, a...) 8 | // and fmt.Println(). 9 | func Printfln(format string, a ...any) (n int, err error) { 10 | n, err = fmt.Printf(format, a...) 11 | if err != nil { 12 | return 13 | } 14 | 15 | m, err := fmt.Println() 16 | n += m 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go101.org/nstd 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /io.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | // CheckWriteResult checks whether or not a Write method is badly implemented. 9 | // 10 | // See: 11 | // 12 | // * https://github.com/golang/go/issues/67921 13 | // * https://github.com/golang/go/issues/9096 14 | func WriteWithCheck(w io.Writer, p []byte) (n int, err error) { 15 | n, err = w.Write(p) 16 | if n < 0 { 17 | n = 0 18 | if err != nil { 19 | err = fmt.Errorf("errBadWrite: n (%d) < 0 with additional error: %w", n, err) 20 | } else { 21 | err = fmt.Errorf("errBadWrite: n (%d) < 0", n) 22 | } 23 | } else if n > len(p) { 24 | n = len(p) 25 | if err != nil { 26 | err = fmt.Errorf("errBadWrite: n (%d) > len (%d) with additional error: %w", n, len(p), err) 27 | } else { 28 | err = fmt.Errorf("errBadWrite: n (%d) > len (%d)", n, len(p)) 29 | } 30 | } else if n < len(p) && err == nil { 31 | err = fmt.Errorf("errBadWrite: n (%d) < len (%d) but no errors", n, len(p)) 32 | } 33 | return 34 | } 35 | 36 | // WriteStringWithBuffer writes a string into an [io.Writer] with a provided buffer. 37 | // The buffer is used to avoid a string-> []byte conversion (which might allocate). 38 | // This function is like [io.CopyBuffer] but much simpler. 39 | func WriteStringWithBuffer(w io.Writer, s string, buffer []byte) (int, error) { 40 | if len(buffer) == 0 { 41 | panic("the buffer is") 42 | } 43 | 44 | var n = 0 45 | for len(s) > 0 { 46 | x := buffer[:copy(buffer, s)] 47 | 48 | _, err := WriteWithCheck(w, x) 49 | n += len(x) 50 | if err != nil { 51 | return n, err 52 | } 53 | 54 | s = s[len(x):] 55 | } 56 | 57 | return n, nil 58 | } 59 | -------------------------------------------------------------------------------- /io_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestWriteStringWithBuffer(t *testing.T) { 9 | testWriteStringWithBuffer("", t) 10 | testWriteStringWithBuffer("a", t) 11 | testWriteStringWithBuffer(" abcdefghijklmnopqrstuvwxyz -", t) 12 | testWriteStringWithBuffer(string([]byte{100000: 'x'}), t) 13 | } 14 | 15 | func testWriteStringWithBuffer(s string, t *testing.T) { 16 | for _, size := range []int{1, 64, 100, 789, 1024} { 17 | buf := make([]byte, size) 18 | var w bytes.Buffer 19 | n, err := WriteStringWithBuffer(&w, s, buf) 20 | if err != nil { 21 | continue 22 | } 23 | if n != len(s) { 24 | t.Fatalf("WriteStringWithBuffer: n (%d) != len(s) (bufer size: %d, s: %s)", n, buf, s) 25 | } 26 | if r := w.String(); r != s { 27 | t.Fatalf("WriteStringWithBuffer: bad write: %s (bufer size: %d, s: %s)", r, buf, s) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /maps.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | // MakeMapOf returns a blank map which has the same type as the input map. 4 | // It is mainly used to avoid writing some verbose map type literals. 5 | // 6 | // * Usage 1: MakeMapOf[MapType](nil, 32) 7 | // * Usage 2: MakeMapOf(aMap, 8) 8 | func MakeMapOf[M ~map[K]E, K comparable, E any](_ M, capHint int) M { 9 | return make(M, capHint) 10 | } 11 | 12 | // CollectMapKeys collects all the keys in a map into a freshly 13 | // created result slice. The length and capacity of the result slice 14 | // are both equal to the length of the map. 15 | // 16 | // See: https://github.com/golang/go/issues/68261 17 | func CollectMapKeys[K comparable, E any](m map[K]E) []K { 18 | if len(m) == 0 { 19 | return nil 20 | } 21 | 22 | var s = make([]K, 0, len(m)) 23 | for k := range m { 24 | s = append(s, k) 25 | } 26 | return s 27 | } 28 | 29 | // AppendMapKeys appends all the keys in a map into the specified slice. 30 | func AppendMapKeys[K comparable, E any](s []K, m map[K]E) []K { 31 | for k := range m { 32 | s = append(s, k) 33 | } 34 | return s 35 | } 36 | 37 | // BoolKeyMap is an optimized version of map[K]E, where K is a bool type. 38 | type BoolKeyMap[K ~bool, E any] struct { 39 | trueE E 40 | falseE E 41 | } 42 | 43 | // Put puts an entry {k, e} into m. 44 | func (m *BoolKeyMap[K, E]) Put(k K, e E) { 45 | if k { 46 | m.trueE = e 47 | } else { 48 | m.falseE = e 49 | } 50 | } 51 | 52 | // Get returns the element indexed by key k. 53 | func (m *BoolKeyMap[K, E]) Get(k K) E { 54 | if k { 55 | return m.trueE 56 | } else { 57 | return m.falseE 58 | } 59 | } 60 | 61 | // BoolElementMap is optimized version of map[K]E, where E is a bool type. 62 | // Entries with false element value will not be put in BoolElementMap maps. 63 | type BoolElementMap[K comparable, E ~bool] struct { 64 | m map[K]blank 65 | } 66 | 67 | // Put puts an entry {k, e} into m. 68 | // Note, if e is false and the corresponding entry exists, the entry is deleted. 69 | func (m *BoolElementMap[K, E]) Put(k K, e E) { 70 | if e { 71 | if m.m == nil { 72 | m.m = make(map[K]blank) 73 | } 74 | m.m[k] = blank{} 75 | } else if m.m != nil { 76 | delete(m.m, k) 77 | } 78 | } 79 | 80 | // Get returns the element indexed by key k. 81 | func (m *BoolElementMap[K, E]) Get(k K) E { 82 | _, has := m.m[k] 83 | return E(has) 84 | } 85 | -------------------------------------------------------------------------------- /maps_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func TestZeroMap(t *testing.T) { 9 | var m = map[int]bool{1: true, 0: false} 10 | { 11 | var bm = MakeMapOf(m, 32) 12 | if len(bm) != 0 { 13 | t.Fatalf("MakeMapOf: len(bm) != 0 (%v)", len(bm)) 14 | } 15 | } 16 | { 17 | var bm = MakeMapOf[map[int]bool](nil, 32) 18 | if len(bm) != 0 { 19 | t.Fatalf("MakeMapOf: len(bm) != 0 (%v)", len(bm)) 20 | } 21 | } 22 | } 23 | 24 | func TestCollectMapKeys(t *testing.T) { 25 | const N = 1024 26 | var m = make(map[int]blank, N) 27 | for range [N]blank{} { 28 | m[rand.Intn(N)] = blank{} 29 | } 30 | var s = CollectMapKeys(m) 31 | if len(s) != len(m) { 32 | t.Fatalf("CollectMapKeys: len(s) != len(m) (%v vs. %v)", len(s), len(m)) 33 | } 34 | } 35 | 36 | func TestAppendMapKeys(t *testing.T) { 37 | const N = 1024 38 | var m = make(map[int]blank, N) 39 | for range [N]blank{} { 40 | m[rand.Intn(N)] = blank{} 41 | } 42 | var s = AppendMapKeys(nil, m) 43 | if len(s) != len(m) { 44 | t.Fatalf("AppendMapKeys: len(s) != len(m) (%v vs. %v)", len(s), len(m)) 45 | } 46 | } 47 | 48 | func TestBoolKeyMap(t *testing.T) { 49 | var m BoolKeyMap[bool, int] 50 | m.Put(true, 123) 51 | m.Put(false, 789) 52 | var x = m.Get(true) 53 | assert(x == 123, "BoolKeyMap: Get(true) != 123 (%d)", x) 54 | var y = m.Get(false) 55 | assert(y == 789, "BoolKeyMap: Get(false) != 789 (%d)", y) 56 | m.Put(true, 321) 57 | m.Put(false, 987) 58 | x = m.Get(true) 59 | assert(x == 321, "BoolKeyMap: Get(true) != 321 (%d)", x) 60 | y = m.Get(false) 61 | assert(y == 987, "BoolKeyMap: Get(false) != 987 (%d)", y) 62 | } 63 | 64 | func TestBoolElementMap(t *testing.T) { 65 | var m BoolElementMap[int, bool] 66 | m.Put(1, false) 67 | m.Put(2, false) 68 | m.Put(3, false) 69 | assert(m.m == nil, "BoolElementMap: m.m == nil") 70 | m.Put(1, true) 71 | assert(len(m.m) == 1, "BoolElementMap: len(m.m) == 1 (%d)", len(m.m)) 72 | m.Put(1, true) 73 | assert(len(m.m) == 1, "BoolElementMap: len(m.m) == 1 (%d)", len(m.m)) 74 | m.Put(2, true) 75 | assert(len(m.m) == 2, "BoolElementMap: len(m.m) == 2 (%d)", len(m.m)) 76 | var x = m.Get(1) 77 | var y = m.Get(2) 78 | var z = m.Get(3) 79 | assert(x, "BoolElementMap: x is false") 80 | assert(y, "BoolElementMap: y is false") 81 | assert(!z, "BoolElementMap: z is true") 82 | m.Put(1, false) 83 | assert(len(m.m) == 1, "BoolElementMap: len(m.m) == 1 (%d)", len(m.m)) 84 | x = m.Get(1) 85 | y = m.Get(2) 86 | z = m.Get(3) 87 | assert(!x, "BoolElementMap: x is true") 88 | assert(y, "BoolElementMap: y is false") 89 | assert(!z, "BoolElementMap: z is true") 90 | } 91 | -------------------------------------------------------------------------------- /nstd.go: -------------------------------------------------------------------------------- 1 | // nstd package provides some missing types and functions 2 | // in the standard library. 3 | package nstd 4 | -------------------------------------------------------------------------------- /numeric.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | // Signed is a constraint that permits any signed integer type. 4 | // If future releases of Go add new predeclared signed integer types, 5 | // this constraint will be modified to include them. 6 | type Signed interface { 7 | ~int | ~int8 | ~int16 | ~int32 | ~int64 8 | } 9 | 10 | // Unsigned is a constraint that permits any unsigned integer type. 11 | // If future releases of Go add new predeclared unsigned integer types, 12 | // this constraint will be modified to include them. 13 | type Unsigned interface { 14 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 15 | } 16 | 17 | // Integer is a constraint that permits any integer type. 18 | // If future releases of Go add new predeclared integer types, 19 | // this constraint will be modified to include them. 20 | type Integer interface { 21 | Signed | Unsigned 22 | } 23 | 24 | // Float is a constraint that permits any floating-point type. 25 | // If future releases of Go add new predeclared floating-point types, 26 | // this constraint will be modified to include them. 27 | type Float interface { 28 | ~float32 | ~float64 29 | } 30 | 31 | // Complex is a constraint that permits any complex numeric type. 32 | // If future releases of Go add new predeclared complex numeric types, 33 | // this constraint will be modified to include them. 34 | type Complex interface { 35 | ~complex64 | ~complex128 36 | } 37 | 38 | type Real interface { 39 | Integer | Float 40 | } 41 | 42 | type Numeric interface { 43 | Integer | Float | Complex 44 | } 45 | 46 | // Sign returns the sign of a value of a [Signed] integer type. 47 | // For a negative integer, it returns -1; 48 | // for a positive integer, it returns 1; 49 | // for 0, it returns 0. 50 | func Sign[T Signed](x T) int { 51 | if x < 0 { 52 | return -1 53 | } 54 | if x > 0 { 55 | return 1 56 | } 57 | return 0 58 | } 59 | -------------------------------------------------------------------------------- /numeric_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSign(t *testing.T) { 8 | testSign[int](100, 1, t) 9 | testSign[int](-23, -1, t) 10 | testSign[int](0, 0, t) 11 | testSign[int32](55, 1, t) 12 | testSign[int32](-1, -1, t) 13 | testSign[int32](0, 0, t) 14 | } 15 | 16 | func testSign[S Signed](v S, sign int, t *testing.T) { 17 | if sn := Sign(v); sn != sign { 18 | t.Fatalf("Sign(%v) != %v (but %v)", v, sign, sn) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ordered.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | // Ordered is a constraint that permits any ordered type: any type 4 | // that supports the operators < <= >= >. 5 | // If future releases of Go add new ordered types, 6 | // this constraint will be modified to include them. 7 | type Ordered interface { 8 | Real | ~string 9 | } 10 | 11 | func minOfTwo[T Ordered](x, y T) T { 12 | if x < y { 13 | return x 14 | } 15 | return y 16 | } 17 | 18 | // Clamp clamps an ordered value within a range. 19 | // Both min and max are inclusive. 20 | // If v is NaN, then NaN is returned. 21 | // 22 | // See: https://github.com/golang/go/issues/58146 23 | func Clamp[T Ordered](v, min, max T) T { 24 | if min > max { 25 | Panicf("min (%v) > max (%v)!", min, max) 26 | } 27 | 28 | if v < min { 29 | return min 30 | } 31 | if v > max { 32 | return max 33 | } 34 | return v 35 | } 36 | -------------------------------------------------------------------------------- /ordered_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestClamp(t *testing.T) { 9 | testClampOfType[int](t) 10 | testClampOfType[uint32](t) 11 | testClampOfType[int64](t) 12 | testClampOfType[float32](t) 13 | testClampOfType[float64](t) 14 | 15 | testClamp(math.NaN(), 0, 1, math.NaN(), t) 16 | testClamp("a", "c", "f", "c", t) 17 | testClamp("x", "c", "f", "f", t) 18 | 19 | defer func() { 20 | if recover() == nil { 21 | t.Fatalf("should panic when min > max") 22 | } 23 | }() 24 | testClamp(math.NaN(), 1, 0, math.NaN(), t) 25 | } 26 | 27 | func testClamp[R Ordered](v, min, max, expected R, t *testing.T) { 28 | if clamped := Clamp(v, min, max); clamped != expected { 29 | if v == v || expected == expected { 30 | t.Fatalf("Clamp(%v, %v, %v) != %v (but %v)", v, min, max, expected, clamped) 31 | } 32 | } 33 | } 34 | 35 | func testClampOfType[R Real](t *testing.T) { 36 | var min R = 2 37 | var max R = 8 38 | 39 | var testCases = []struct { 40 | v, clamped R 41 | }{ 42 | {1, 2}, 43 | {2, 2}, 44 | {5, 5}, 45 | {8, 8}, 46 | {9, 8}, 47 | } 48 | 49 | for _, tc := range testCases { 50 | testClamp(tc.v, min, max, tc.clamped, t) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /os.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package nstd 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | // CopyDir copies a directory. 10 | func CopyDir(dest, src string) error { 11 | return os.CopyFS(dest, os.DirFS(src)) 12 | } 13 | -------------------------------------------------------------------------------- /reflect.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Type returns a reflect.Type which represents type T, 8 | // which may be either an non-interface type or interface type. 9 | func Type[T any]() reflect.Type { 10 | var v T 11 | return ValueOf(v).Type() 12 | } 13 | 14 | // TypeOf returns a reflect.Type which represents the type of v, 15 | // which may be either an non-interface value or interface value. 16 | func TypeOf[T any](v T) reflect.Type { 17 | return ValueOf(v).Type() 18 | } 19 | 20 | // Value returns a reflect.Value which represents the value v, 21 | // which may be either an non-interface value or interface value. 22 | func ValueOf[T any](v T) reflect.Value { 23 | // make sure r.CanAddr() and r.CanSet() both always return false, 24 | // even if the passed argument is an interface. 25 | return reflect.ValueOf([1]T{v}).Index(0) 26 | } 27 | -------------------------------------------------------------------------------- /reflect_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestReflect(t *testing.T) { 9 | var v = 123 10 | var x any = v 11 | 12 | if TypeOf(x).Kind() != reflect.Interface { 13 | t.Fatal("type of x should be Inferface") 14 | } 15 | if ValueOf(x).Kind() != reflect.Interface { 16 | t.Fatal("type of x should be Inferface") 17 | } 18 | if Type[any]().Kind() != reflect.Interface { 19 | t.Fatal("any should be Inferface") 20 | } 21 | 22 | if TypeOf(v).Kind() == reflect.Interface { 23 | t.Fatal("type of v should not be Inferface") 24 | } 25 | if ValueOf(v).Kind() == reflect.Interface { 26 | t.Fatal("type of v should not be Inferface") 27 | } 28 | if Type[int]().Kind() == reflect.Interface { 29 | t.Fatal("int should not be Inferface") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /slices.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | // MakeSlice makes a slice with the specified length. 4 | // Different from the built-in make function, the capacity 5 | // of the result slice might be larger than the length. 6 | func MakeSlice[S ~[]E, E any](len int) S { 7 | return append(S(nil), make(S, len)...) 8 | } 9 | 10 | // MakeSliceWithMinCap makes a slice with capacity not smaller than cap. 11 | // The length of the result slice is zero. 12 | // 13 | // See: https://github.com/golang/go/issues/69872 14 | func MakeSliceWithMinCap[S ~[]E, E any](cap int) S { 15 | return append(S(nil), make(S, cap)...)[:0] 16 | } 17 | 18 | type _slice[S ~[]T, T any] []T 19 | 20 | // Slice returns an internal representation of s to do some operations. 21 | // Call methods of the internal representation to perform the operations. 22 | func Slice[S ~[]T, T any](s S) _slice[S, T] { 23 | return []T(s) 24 | } 25 | 26 | // Unnamed converts s to unnamed type. 27 | func (s _slice[S, T]) Unnamed() []T { 28 | return s 29 | } 30 | 31 | // Clone clones s and converts the clone result to S. 32 | // Different from [slices.Clone], the result of Clone always has equal length and capacity. 33 | func (s _slice[S, T]) Clone() S { 34 | var r = make([]T, len(s)) 35 | copy(r, s) 36 | return r 37 | } 38 | 39 | // RefIter is an iterator which iterates references of elements of s. 40 | func (s _slice[S, T]) RefIter(yield func(*T, int) bool) { 41 | for i := range s { 42 | if !yield(&s[i], i) { 43 | return 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /slices_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMakeSlice(t *testing.T) { 8 | const N = 1024 9 | var m = make(map[int]blank, N) 10 | for i := range [N]blank{} { 11 | m[cap(MakeSlice[[]byte](i))] = blank{} 12 | } 13 | if len(m) == N { 14 | t.Fatalf("MakeSlice: len(m) == N (%v vs. %v)", len(m), N) 15 | } 16 | } 17 | 18 | func TestMakeSliceWithMinCap(t *testing.T) { 19 | var caps = []int{0, 1, 5, 8, 11, 12, 33, 35, 99, 1020, 1021, 1023, 1024} 20 | var n = 0 21 | for _, c := range caps { 22 | var s = MakeSliceWithMinCap[[]bool](c) 23 | if len(s) != 0 { 24 | t.Fatalf("MakeSliceWithMinCap: len(s) == 0 (len=%d, c=%d)", len(s), c) 25 | } 26 | if cap(s) < c { 27 | t.Fatalf("MakeSliceWithMinCap: cap(s) is too small (cap=%d, c=%d)", cap(s), c) 28 | } 29 | if cap(s) > c { 30 | n++ 31 | } 32 | } 33 | if n == 0 { 34 | t.Fatal("MakeSliceWithMinCap: no capacity larger than min") 35 | } 36 | } 37 | 38 | func TestSlice(t *testing.T) { 39 | type S []int 40 | var x = S{1, 2, 3} 41 | var y = Slice(x).Clone() 42 | if TypeOf(x) != TypeOf(y) { 43 | t.Fatalf("SliceClone: TypeOf(x) != TypeOf(y)\n\t%s\n\t%s", TypeOf(x), TypeOf(y)) 44 | } 45 | for v := range Slice(x).RefIter { 46 | *v *= 2 47 | } 48 | for i := range x { 49 | if x[i] != y[i]*2 { 50 | t.Fatalf("SliceClone: x[%d] != y[%d] * 2 (%d : %d)", i, i, x[i], y[i]) 51 | } 52 | } 53 | 54 | var z = Slice(x).Unnamed() 55 | if TypeOf(x) == TypeOf(z) { 56 | t.Fatalf("SliceClone: TypeOf(x) == TypeOf(z)\n\t%s\n\t%s", TypeOf(x), TypeOf(z)) 57 | } 58 | z[0] = 99 59 | if x[0] != z[0] { 60 | t.Fatalf("SliceClone: x[0] != z[0] (%d : %d)", x[0], z[0]) 61 | } 62 | if y[0] == z[0] { 63 | t.Fatalf("SliceClone: y[0] == z[0] (%d : %d)", y[0], z[0]) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sync.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Methods of *Mutex return a *Mutex result, so that 8 | // these methods may be called in a chain. 9 | // It is just a simpler wrapper of the [sync.Mutex]. 10 | // The main purpose of this type is to support 11 | // the following use case: 12 | // 13 | // var aMutex nstd.Mutex 14 | // 15 | // func foo() { 16 | // defer aMutex.Lock().Unlock() 17 | // ... // do something 18 | // } 19 | type Mutex struct { 20 | mu sync.Mutex 21 | } 22 | 23 | // Lock return m, so that the methods of m can be called in chain. 24 | func (m *Mutex) Lock() *Mutex { 25 | m.mu.Lock() 26 | return m 27 | } 28 | 29 | // Unlock return m, so that the methods of m can be called in chain. 30 | func (m *Mutex) Unlock() *Mutex { 31 | m.mu.Unlock() 32 | return m 33 | } 34 | 35 | // Do guards the execution of a function in Lock() and Unlock() 36 | // 37 | // See: https://github.com/golang/go/issues/63941 38 | func (m *Mutex) Do(f func()) { 39 | defer m.Lock().Unlock() 40 | f() 41 | } 42 | 43 | // WaitGroup extends sync.WaitGroup. 44 | // Each WaitGroup maintains an internal count which initial value is zero. 45 | type WaitGroup struct { 46 | wg sync.WaitGroup 47 | } 48 | 49 | // GoN starts several concurrent tasks and increases the internal count by len(fs). 50 | // The internal count will be descreased by one when each of the task is done. 51 | // 52 | // See: https://github.com/golang/go/issues/18022 53 | func (wg *WaitGroup) Go(fs ...func()) { 54 | for i, f := range fs { 55 | if f == nil { 56 | Panicf("fs[%d] is nil", i) 57 | } 58 | } 59 | wg.wg.Add(len(fs)) 60 | for _, f := range fs { 61 | f := f 62 | go func() { 63 | defer wg.wg.Done() 64 | f() 65 | }() 66 | } 67 | } 68 | 69 | // GoN starts a task n times concurrently and increases the internal count by n. 70 | // The internal count will be descreased by one when each of the task instances is done. 71 | func (wg *WaitGroup) GoN(n int, f func()) { 72 | if n < 0 { 73 | panic("the count must not be negative") 74 | } 75 | if f == nil { 76 | panic("f is nil") 77 | } 78 | wg.wg.Add(n) 79 | for i := 0; i < n; i++ { 80 | go func() { 81 | defer wg.wg.Done() 82 | f() 83 | }() 84 | } 85 | } 86 | 87 | // Wait blocks until the internal counter is zero. 88 | func (wg *WaitGroup) Wait() { 89 | wg.wg.Wait() 90 | } 91 | 92 | // WaitChannel returns a channel which reads will block until the internal counter is zero. 93 | func (wg *WaitGroup) WaitChannel() <-chan struct{} { 94 | var c = make(chan struct{}) 95 | 96 | go func() { 97 | wg.wg.Wait() 98 | close(c) 99 | }() 100 | 101 | return c 102 | } 103 | -------------------------------------------------------------------------------- /sync_test.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMutexAndWaitGroup(t *testing.T) { 8 | const N = 1000 9 | var n = 0 10 | defer func() { 11 | if expected := N * 12; n != expected { 12 | t.Fatalf("n != %d", expected) 13 | } 14 | }() 15 | 16 | var wg WaitGroup 17 | 18 | var f = func() { 19 | var m Mutex 20 | for range [1000]struct{}{} { 21 | wg.Go(func() { 22 | defer m.Lock().Unlock() 23 | n += 1 24 | }, func() { 25 | defer m.Lock().Unlock() 26 | n += 2 27 | }) 28 | wg.GoN(3, func() { 29 | defer m.Lock().Unlock() 30 | n += 1 31 | }) 32 | } 33 | } 34 | 35 | f() 36 | <-wg.WaitChannel() 37 | 38 | defer wg.Wait() 39 | f() 40 | } 41 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | package nstd 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | // ElapsedTimeLogFunc returns a function which prints the duration elapsed 9 | // since the time the function is invoked. 10 | // 11 | // Use example: 12 | // 13 | // logElapsedTime := nstd.ElapsedTimeLogFunc("") 14 | // ... // do task 1 15 | // logElapsedTime("task 1:") 16 | // ... // do task 1 17 | // logElapsedTime("task 2:") 18 | func ElapsedTimeLogFunc(commonPrefix string) func(prefix string) bool { 19 | var x string 20 | if commonPrefix != "" { 21 | x = " " 22 | } 23 | var start = time.Now() 24 | return func(prefix string) bool { 25 | var y string 26 | if commonPrefix != "" { 27 | y = " " 28 | } 29 | log.Printf("%s%s%s%s%s", commonPrefix, x, prefix, y, time.Since(start)) 30 | return true 31 | } 32 | } 33 | --------------------------------------------------------------------------------