Collections and Utility Packages
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Go 1.27 (go1.27.2), checked on 2026-10-18. Every runnable program below was vetted with go vet,
compiled and run with that toolchain, and the output (including compiler errors, vet reports and panics) is the real output.
Go deliberately keeps its core small: the language gives you slices and maps, and the standard library adds the rest as
ordinary packages. This page is a tour of those packages that you reach for every day once a program grows past
fmt and net/http: the generic helpers in slices, maps and cmp; the older sort package you will still meet in existing code;
the three container/... packages; floating-point, arbitrary-precision and bit-level arithmetic; random numbers, both the
fast kind and the cryptographically secure kind; the uuid package that arrived in Go 1.27; and the two low-level helpers
hash/maphash and unique. The examples use the library catalogue domain (shelf) of this reference.
The page assumes the basics of Arrays and Slices and
Maps, and the generics syntax from
Generics. Iterator-returning functions such as slices.Values and
maps.Keys are explained in Iterators and Range-over-Func.
Cryptographic hashing, keys and TLS are on Crypto and Security;
this page only covers the random-number side of crypto/rand. If you come from Rust, the equivalents of much of this
page are on Collections and
Numbers and Numeric Computing; from C, see
Numbers and Math.
The packages at a glance
| Package | What it is for | Since |
|---|---|---|
|
|
1.21 ( |
|
Generic functions on slices: sort, search, insert, delete, compare, iterator adapters |
1.21 (iterators 1.23) |
|
Generic functions on maps: clone, copy, equal, delete, iterator adapters |
1.21 (iterators 1.23) |
|
The pre-generics sorting API, based on |
1.0 |
|
Priority queue algorithms over a user type that implements |
1.0 |
|
Doubly linked list |
1.0 |
|
Circular list |
1.0 |
|
Constants, floating-point functions, IEEE 754 helpers |
1.0 |
|
Arbitrary-precision |
1.0 ( |
|
Bit counting, rotation, reversal and carry arithmetic on fixed-size unsigned integers |
1.9 |
|
Fast pseudo-random numbers: |
1.22 ( |
|
Cryptographically secure random bytes, integers and text |
1.0 ( |
|
RFC 9562 UUIDs: versions 4 and 7, parsing, comparison and text marshaling |
1.27 |
|
Seeded, collision-resistant hashing of bytes and comparable values; the |
1.14 ( |
|
Canonicalizing (interning) comparable values with |
1.23 |
cmp: ordering and defaults
The cmp package is the vocabulary that slices and maps share. It defines:
-
the constraint
cmp.Ordered, the set of all types that support the ordering operators<,<=,>and>=: every integer type, every floating-point type andstring, together with any defined type whose underlying type is one of those (the~forms in the constraint); -
cmp.Compare(x, y), a three-way comparison that returns a negative number, zero or a positive number (in practice -1, 0 or +1); -
cmp.Less(x, y), the boolean form; and -
cmp.Or(vals...), which returns the first argument that is not the zero value of its type (Go 1.22).
Three-way comparison is the signature of every Func function in slices: SortFunc, BinarySearchFunc, MinFunc,
CompactFunc and so on take func(a, b E) int. That shape composes, which is what cmp.Or is for: a multi-key ordering
is the first comparison that is not zero.
package main
import (
"cmp"
"fmt"
"math"
"slices"
)
type Config struct {
Addr string
Timeout int
}
func main() {
fmt.Println(cmp.Compare(1, 2), cmp.Compare("b", "a"), cmp.Compare(3.5, 3.5))
fmt.Println(cmp.Less("apple", "banana"))
// NaN sorts before every other float; NaN equals NaN for Compare.
nan := math.NaN()
fmt.Println(cmp.Compare(nan, 1), cmp.Compare(nan, nan), cmp.Compare(1, nan))
fmt.Println(cmp.Less(nan, math.Inf(-1)), nan < math.Inf(-1))
// -0.0 and +0.0 compare equal.
fmt.Println(cmp.Compare(math.Copysign(0, -1), 0))
floats := []float64{3, nan, 1, math.Inf(1), -2, math.Inf(-1)}
slices.Sort(floats)
fmt.Println(floats)
// cmp.Or returns the first argument that is not the zero value.
def := Config{Addr: ":8080", Timeout: 30}
user := Config{Timeout: 5}
merged := Config{
Addr: cmp.Or(user.Addr, def.Addr),
Timeout: cmp.Or(user.Timeout, def.Timeout),
}
fmt.Printf("%+v\n", merged)
fmt.Println(cmp.Or(0, 0, 0) == 0, cmp.Or[string]() == "")
// It works for any comparable type, e.g. pointers and structs.
fmt.Println(cmp.Or(Config{}, def) == def)
}
-1 1 0
true
-1 0 1
true false
0
[NaN -Inf -2 1 3 +Inf]
{Addr::8080 Timeout:5}
true true
true
Source: cmp package
Points to remember:
-
Floating-point rules.
cmp.Compareandslices.Sortdefine a total order for floats that the<operator does not: a NaN is less than any non-NaN and equal to another NaN, and-0.0equals+0.0. That is why theSortcall above putsNaNfirst and does not corrupt the result. With a hand-writtena < bcomparison, any NaN makes the ordering inconsistent. -
cmp.Orevaluates every argument. It is a function, not an operator, socmp.Or(expensive(), fallback())calls both. It also treats a legitimate zero as "unset": aTimeoutof 0 meaning "no timeout" cannot be distinguished from a missing value. For such fields use a pointer or a separatebool. -
Orneedscomparable. It compares each argument with the zero value using==, so slices, maps and functions are rejected at compile time. -
Do not subtract. The older idiom
return a.Year - b.Yearworks for small numbers but overflows for large or extreme ones:
package main
import (
"cmp"
"fmt"
"math"
)
func main() {
a, b := math.MaxInt-1, -5
fmt.Println(a-b > 0) // wraps: wrongly reports a < b
fmt.Println(cmp.Compare(a, b)) // correct: 1
}
false
1
cmp.Compare is the right replacement for the subtraction. Passing a type that is not ordered, or a non-comparable one to cmp.Or,
fails at compile time with the type set in the message. (In the error demos on this page the fragments omit the package clause and imports,
so the line numbers in the messages refer to the complete file.)
type Book struct {
Title string
Year int
}
books := []Book{{"Emma", 1815}, {"Dune", 1965}}
slices.Sort(books) // Book is not an ordered type
fmt.Println(cmp.Compare(books[0], books[1]))
fmt.Println(cmp.Or([]int{}, []int{1})) // slices are not comparable
./main.go:16:13: Book does not satisfy cmp.Ordered (Book missing in ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string)
./main.go:17:25: Book does not satisfy cmp.Ordered (Book missing in ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string)
./main.go:18:20: []int does not satisfy comparable
Source: cmp.Compare and cmp.Or
Likewise a "less" function is the wrong shape for the Func variants, which want an int:
slices.SortFunc(s, func(a, b int) bool { return a < b })
./main.go:11:21: in call to slices.SortFunc, type func(a int, b int) bool of func(a, b int) bool {…} does not match inferred type func(a int, b int) int for func(a E, b E) int
Source: cmp.Ordered
slices and maps: a reference
The generic functions in slices and maps replaced most hand-written loops. The behavior of slices, append and capacity is on
Arrays and Slices and the map semantics on
Maps; this section is the complete reference table, followed by an
end-to-end example on the shelf domain and the pitfalls.
slices
| Function | What it does | Since |
|---|---|---|
|
Sorts an ordered slice in place; NaNs first |
1.21 |
|
Sorts with a three-way |
1.21 |
|
Like |
1.21 |
|
Reports whether the slice is already sorted |
1.21 |
|
Index of |
1.21 |
|
The same with a custom comparison; the target may have another type than the elements |
1.21 |
|
Smallest and largest element; panics on an empty slice |
1.21 |
|
The same with a custom comparison; the first of equal elements wins |
1.21 |
|
Same length and equal elements |
1.21 |
|
Lexicographic three-way comparison |
1.21 |
|
First matching position or -1 |
1.21 |
|
Membership test |
1.21 |
|
Inserts values before index |
1.21 |
|
Removes |
1.21 (zeroing 1.22) |
|
Removes every element for which |
1.21 |
|
Replaces |
1.21 |
|
Removes consecutive duplicates |
1.21 |
|
Shallow copy with a new backing array; |
1.21 |
|
Reduces capacity to length so the next |
1.21 |
|
Guarantees room for |
1.21 |
|
Reverses in place |
1.21 |
|
New slice with all the elements of the arguments |
1.22 |
|
New slice repeating |
1.23 |
|
Iterators over index-value pairs, values or in reverse |
1.23 |
|
Drain an iterator into a new or existing slice |
1.23 |
|
Collect an iterator and sort the result |
1.23 |
|
Iterator over consecutive sub-slices of length |
1.23 |
maps
| Function | What it does | Since |
|---|---|---|
|
Shallow copy; |
1.21 |
|
Copies all entries of |
1.21 |
|
Same keys and equal values |
1.21 |
|
Deletes every entry for which |
1.21 |
|
Iterators in the map’s random order |
1.23 |
|
Adds the pairs of an iterator |
1.23 |
|
New map from an iterator of pairs |
1.23 |
Source: slices package
Source: maps package
The next program combines them on the shelf domain: a multi-key sort with cmp.Or, a binary search by ISBN, grouping, ordered
iteration over a map with slices.Sorted(maps.Keys(m)), MinFunc and Chunk for paging.
package main
import (
"cmp"
"fmt"
"maps"
"slices"
"strings"
)
// Book is a trimmed-down catalog.Book for the example.
type Book struct {
ISBN string
Title string
Author string
Year int
}
func main() {
books := []Book{
{"9780441013593", "Dune", "Herbert", 1965},
{"9780141439518", "Emma", "Austen", 1815},
{"9780141439884", "Persuasion", "Austen", 1817},
{"9780199535569", "Ulysses", "Joyce", 1922},
{"9780141439471", "Dubliners", "Joyce", 1914},
}
// Multi-key ordering: author, then year descending, then title.
slices.SortFunc(books, func(a, b Book) int {
return cmp.Or(
strings.Compare(a.Author, b.Author),
cmp.Compare(b.Year, a.Year), // reversed operands = descending
cmp.Compare(a.Title, b.Title),
)
})
for _, b := range books {
fmt.Printf("%-8s %d %s\n", b.Author, b.Year, b.Title)
}
// Binary search needs the same ordering on the searched key only.
byISBN := slices.SortedFunc(slices.Values(books), func(a, b Book) int {
return cmp.Compare(a.ISBN, b.ISBN)
})
i, found := slices.BinarySearchFunc(byISBN, "9780199535569", func(b Book, isbn string) int {
return cmp.Compare(b.ISBN, isbn)
})
fmt.Println(i, found, byISBN[i].Title)
// Group titles by author, then walk the authors in order.
titles := map[string][]string{}
for _, b := range books {
titles[b.Author] = append(titles[b.Author], b.Title)
}
for _, author := range slices.Sorted(maps.Keys(titles)) {
fmt.Println(author, titles[author])
}
oldest := slices.MinFunc(books, func(a, b Book) int { return cmp.Compare(a.Year, b.Year) })
fmt.Println("oldest:", oldest.Title)
for page := range slices.Chunk(slices.Sorted(maps.Keys(titles)), 2) {
fmt.Println("page:", page)
}
}
Austen 1817 Persuasion
Austen 1815 Emma
Herbert 1965 Dune
Joyce 1922 Ulysses
Joyce 1914 Dubliners
3 true Ulysses
Austen [Persuasion Emma]
Herbert [Dune]
Joyce [Ulysses Dubliners]
oldest: Emma
page: [Austen Herbert]
page: [Joyce]
Source: The Go Blog: Go 1.21 Is Released
Source: Go 1.23 release notes: Iterators
Notes on the program:
-
In
SortFuncthe order of the operands picks the direction:cmp.Compare(b.Year, a.Year)sorts newest first. -
BinarySearchFunconly requires that the slice is sorted by the searched key; the program re-sorts a copy by ISBN first. The comparison function receives an element and the target, in that order. -
Maps iterate in a random order, so the program sorts the keys before printing or chunking them. Without the sort, the pages would differ from run to run.
-
SortandSortFuncare not stable. UseSortStableFuncwhen records that compare equal must keep their input order (for example when sorting a table by one column after sorting by another).
Pitfalls
-
The result must be used.
Insert,Delete,Compact,Replace,Grow,Clipand friends return the new slice, because the length (and maybe the backing array) changes.go vetreports a call whose result is dropped:
s := []int{1, 2, 3}
slices.Delete(s, 0, 1)
slices.Sort(s)
slices.Insert(s, 0, 9)
./main.go:10:2: result of slices.Delete call not used
./main.go:12:2: result of slices.Insert call not used
Source: slices.Delete and slices.Insert
(Sort returns nothing, so it is not reported.)
-
Sorted input is a precondition, not a check.
BinarySearchon an unsorted slice neither panics nor errors; it returns an arbitrary answer.Compactremoves only neighbours, so it needs a sorted slice to remove every duplicate. -
Aliasing.
DeleteandInsertmodify the backing array of the argument; the old slice header now shows a shifted array.Clonefirst when the original must survive, andClipto force a copy on the nextappend. -
Out-of-range arguments panic with the runtime’s messages, as the program below shows.
package main
import (
"fmt"
"slices"
)
func try(name string, f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%s: panic: %v\n", name, r)
}
}()
f()
}
func main() {
// Delete shifts the tail down and (since Go 1.22) zeroes the vacated slots,
// so pointers there no longer keep objects alive.
s := []*int{new(int), new(int), new(int), new(int)}
t := slices.Delete(s, 1, 3)
fmt.Println(len(t), len(s), s[2] == nil, s[3] == nil)
// Compact only removes *consecutive* duplicates.
fmt.Println(slices.Compact([]int{1, 1, 2, 1, 1, 3, 3}))
// BinarySearch on an unsorted slice is not an error: it just returns nonsense.
i, found := slices.BinarySearch([]int{9, 1, 8, 2, 7}, 2)
fmt.Println(i, found)
// Clip and the append aliasing trap.
base := make([]int, 3, 10)
grown := append(base, 99)
clipped := append(slices.Clip(base), 100) // capacity 3: forces a new array
fmt.Println(grown[3], clipped[3], len(base), cap(slices.Clip(base)))
try("Insert", func() { _ = slices.Insert([]int{1, 2}, 5, 9) })
try("Min", func() { _ = slices.Min([]int{}) })
try("Chunk", func() {
for range slices.Chunk([]int{1}, 0) {
}
})
try("Delete", func() { _ = slices.Delete([]int{1, 2, 3}, 2, 1) })
try("Repeat", func() { _ = slices.Repeat([]int{1}, -1) })
}
2 4 true true
[1 2 1 3]
2 false
99 100 3 3
Insert: panic: runtime error: slice bounds out of range [5:2]
Min: panic: slices.Min: empty list
Chunk: panic: cannot be less than 1
Delete: panic: runtime error: slice bounds out of range [2:1:]
Repeat: panic: cannot be negative
Source: Go 1.22 release notes: slices
Since Go 1.22, Delete, DeleteFunc, Compact, CompactFunc and Replace zero the elements between the new and old length,
which lets the garbage collector free objects that a slice of pointers would otherwise keep alive.
sort: the legacy package
The sort package predates generics. It has three layers:
-
the interface
sort.Interface(Len,Less(i, j int) bool,Swap(i, j int)) withsort.Sort,sort.Stable,sort.IsSortedand thesort.Reverseadapter; -
the convenience function
sort.Slice(andSliceStable,SliceIsSorted), which takes any slice asanyplus alessfunction over indices and swaps elements withreflect.Swapper; and -
typed helpers:
sort.Ints,sort.Strings,sort.Float64s, theIntSlice,StringSliceandFloat64Slicetypes, and the searchessort.Search,sort.SearchInts,sort.SearchStrings,sort.SearchFloat64sandsort.Find(Go 1.19).
package main
import (
"fmt"
"slices"
"sort"
"strings"
)
type Book struct {
Title string
Year int
}
// byYear implements sort.Interface, the pre-generics way.
type byYear []Book
func (s byYear) Len() int { return len(s) }
func (s byYear) Less(i, j int) bool { return s[i].Year < s[j].Year }
func (s byYear) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
books := []Book{{"Ulysses", 1922}, {"Emma", 1815}, {"Dune", 1965}, {"Dubliners", 1914}}
// 1. sort.Slice: reflection-based swapper, `less` gets indices.
sort.Slice(books, func(i, j int) bool { return books[i].Year < books[j].Year })
fmt.Println(books)
// 2. sort.Sort with a named type implementing sort.Interface.
sort.Sort(sort.Reverse(byYear(books)))
fmt.Println(books)
// 3. The modern replacement: generic, no reflection, three-way compare.
slices.SortFunc(books, func(a, b Book) int { return a.Year - b.Year })
fmt.Println(books)
// Typed helpers for the basic types.
ints := []int{5, 2, 8}
sort.Ints(ints)
words := strings.Fields("pear apple fig")
sort.Strings(words)
fmt.Println(ints, words, sort.IntsAreSorted(ints))
// sort.Search: smallest index in [0,n) where f is true (f must be monotone).
years := []int{1815, 1914, 1922, 1965}
i := sort.Search(len(years), func(i int) bool { return years[i] >= 1920 })
fmt.Println(i, years[i])
// Not found: Search returns n, so check the bounds yourself.
j := sort.Search(len(years), func(i int) bool { return years[i] >= 2000 })
fmt.Println(j, j == len(years))
// sort.SearchInts and slices.BinarySearch also report whether it was found.
k := sort.SearchInts(years, 1914)
fmt.Println(k, k < len(years) && years[k] == 1914)
pos, ok := slices.BinarySearch(years, 1914)
fmt.Println(pos, ok)
// sort.Find (Go 1.19) is the three-way variant and reports found.
pos, ok = sort.Find(len(years), func(i int) int { return 1922 - years[i] })
fmt.Println(pos, ok)
// Stability: sort.SliceStable keeps equal elements in input order.
people := []struct {
Name string
Age int
}{{"Ann", 30}, {"Bob", 25}, {"Cat", 30}, {"Dan", 25}}
sort.SliceStable(people, func(i, j int) bool { return people[i].Age < people[j].Age })
fmt.Println(people)
defer func() { fmt.Println("recovered:", recover()) }()
sort.Slice("not a slice", func(i, j int) bool { return false })
}
[{Emma 1815} {Dubliners 1914} {Ulysses 1922} {Dune 1965}]
[{Dune 1965} {Ulysses 1922} {Dubliners 1914} {Emma 1815}]
[{Emma 1815} {Dubliners 1914} {Ulysses 1922} {Dune 1965}]
[2 5 8] [apple fig pear] true
2 1922
4 true
1 true
1 true
2 true
[{Bob 25} {Dan 25} {Ann 30} {Cat 30}]
recovered: reflect: call of Swapper on string Value
Source: sort package
The program shows the three ways to sort, then the search functions. sort.Search(n, f) returns the smallest index i in
[0, n) for which f(i) is true, assuming f is false and then true across the range; it returns n when nothing matches, so the
caller checks the bound and the equality. sort.Find reports found like slices.BinarySearch.
Which one should new code use?
| Legacy | Modern replacement | Why |
|---|---|---|
|
|
Since Go 1.22 the |
|
|
Generic, no reflection, type-checked, elements passed by value |
|
|
Same, stable |
|
|
No named type with three methods |
|
|
Return |
|
keep, if the type already implements |
A data structure that is not a slice (for example a type that wraps a database cursor) still needs the interface |
The sort.Slice family has two traps that the generic versions remove: passing something that is not a slice compiles but panics
at run time (the last line of the program shows the panic message), and the less function indexes the original slice
variable, so it silently sorts wrongly if the closure refers to a different slice than the one being sorted.
Both sort.Slice and slices.SortFunc require a consistent ordering: less(a, b) and less(b, a) must not both be true, and the
relation must be transitive. A comparison like <= breaks that rule.
container/heap
container/heap implements a binary min-heap on any type that satisfies heap.Interface:
type Interface interface {
sort.Interface // Len, Less, Swap
Push(x any) // add x as element Len()
Pop() any // remove and return element Len()-1
}
Source: container/heap: Interface
You implement the five methods on a slice type, and the package supplies heap.Init, heap.Push, heap.Pop, heap.Fix and
heap.Remove. The Push and Pop methods you write only append to and truncate the slice; you never call them
directly. The heap.Push and heap.Pop functions call them and then restore the heap order, in O(log n).
The smallest element, according to Less, is at index 0.
The next program is a queue of loans ordered by due date. Each element stores its own index so a renewal can call heap.Fix
and a cancellation can call heap.Remove in O(log n).
package main
import (
"container/heap"
"fmt"
"time"
)
// Loan is a due-date entry; index is maintained by the heap for Fix/Remove.
type Loan struct {
Member string
Book string
Due time.Time
index int
}
// dueQueue is a min-heap of loans ordered by due date.
type dueQueue []*Loan
func (q dueQueue) Len() int { return len(q) }
func (q dueQueue) Less(i, j int) bool { return q[i].Due.Before(q[j].Due) }
func (q dueQueue) Swap(i, j int) {
q[i], q[j] = q[j], q[i]
q[i].index = i
q[j].index = j
}
// Push and Pop are called by package heap; do not call them directly.
func (q *dueQueue) Push(x any) {
l := x.(*Loan)
l.index = len(*q)
*q = append(*q, l)
}
func (q *dueQueue) Pop() any {
old := *q
n := len(old)
l := old[n-1]
old[n-1] = nil // let the GC reclaim the loan
l.index = -1
*q = old[:n-1]
return l
}
func main() {
day := func(d int) time.Time { return time.Date(2026, time.November, d, 0, 0, 0, 0, time.UTC) }
q := &dueQueue{}
heap.Init(q)
loans := map[string]*Loan{
"dune": {Member: "ann", Book: "Dune", Due: day(20)},
"emma": {Member: "bob", Book: "Emma", Due: day(5)},
"ulysses": {Member: "cat", Book: "Ulysses", Due: day(12)},
"dubliners": {Member: "dan", Book: "Dubliners", Due: day(30)},
}
for _, k := range []string{"dune", "emma", "ulysses", "dubliners"} {
heap.Push(q, loans[k])
}
fmt.Println("next due:", (*q)[0].Book, (*q)[0].Due.Format("Jan 2"))
// Renewal: change the key, then restore the heap invariant with Fix.
loans["emma"].Due = day(25)
heap.Fix(q, loans["emma"].index)
// Remove an arbitrary element by its index.
heap.Remove(q, loans["ulysses"].index)
for q.Len() > 0 {
l := heap.Pop(q).(*Loan)
fmt.Println(l.Due.Format("Jan 2"), l.Book, l.Member)
}
}
next due: Emma Nov 5
Nov 20 Dune ann
Nov 25 Emma bob
Nov 30 Dubliners dan
Source: container/heap package
| Function | Cost | Use |
|---|---|---|
|
O(n) |
Establish the heap order in a slice that was filled directly |
|
O(log n) |
Insert |
|
O(log n) |
Remove and return the minimum |
|
O(log n) |
Re-establish the order after the element at |
|
O(log n) |
Remove the element at |
Common mistakes:
-
Pointer receivers.
PushandPopchange the slice length, so they need a pointer receiver, whileLen,LessandSwapcan use a value receiver. A missing method is a compile error that names the method:
type q []int
func (q q) Len() int { return len(q) }
func (q q) Less(i, j int) bool { return q[i] < q[j] }
func (q q) Swap(i, j int) { q[i], q[j] = q[j], q[i] }
func (q *q) Push(x any) { *q = append(*q, x.(int)) }
func main() {
h := &q{}
heap.Push(h, 1)
}
./main.go:14:12: cannot use h (variable of type *q) as heap.Interface value in argument to heap.Push: *q does not implement heap.Interface (missing method Pop)
Source: container/heap: Interface
-
Mutating the key without
Fix. ChangingDueon an element that is in the heap silently breaks the invariant. -
Calling the methods instead of the functions.
h.Push(x)appends without sifting; always useheap.Push(h, x). -
Not clearing the popped slot.
Popabove setsold[n-1] = nilso the garbage collector can free the loan. -
anyeverywhere. The interface predates generics, soPop()returnsanyand needs a type assertion. A small generic wrapper hides that and takes the ordering as a function:
package main
import (
"container/heap"
"fmt"
)
// PQ is a small generic wrapper that hides the any-typed heap API.
type PQ[T any] struct{ h inner[T] }
type inner[T any] struct {
items []T
less func(a, b T) bool
}
func (h inner[T]) Len() int { return len(h.items) }
func (h inner[T]) Less(i, j int) bool { return h.less(h.items[i], h.items[j]) }
func (h inner[T]) Swap(i, j int) { h.items[i], h.items[j] = h.items[j], h.items[i] }
func (h *inner[T]) Push(x any) { h.items = append(h.items, x.(T)) }
func (h *inner[T]) Pop() any {
n := len(h.items)
x := h.items[n-1]
var zero T
h.items[n-1] = zero
h.items = h.items[:n-1]
return x
}
func NewPQ[T any](less func(a, b T) bool) *PQ[T] {
return &PQ[T]{h: inner[T]{less: less}}
}
func (p *PQ[T]) Push(x T) { heap.Push(&p.h, x) }
func (p *PQ[T]) Pop() T { return heap.Pop(&p.h).(T) }
func (p *PQ[T]) Len() int { return p.h.Len() }
func main() {
pq := NewPQ(func(a, b string) bool { return len(a) < len(b) })
for _, s := range []string{"Persuasion", "Emma", "Dubliners", "Ulysses"} {
pq.Push(s)
}
for pq.Len() > 0 {
fmt.Print(pq.Pop(), " ")
}
fmt.Println()
}
Emma Ulysses Dubliners Persuasion
The standard library has no generic heap; the wrapper above, a priority queue from a third-party module, or sorting a slice once are the usual options. A heap is also not a sorted slice: only the minimum is guaranteed to be at index 0, and iterating the underlying slice shows the internal order. It is not stable either (equal priorities pop in an unspecified order, which is why the program uses titles of different lengths).
container/list and container/ring
container/list is a doubly linked list. Its zero value is an empty list ready to use, PushFront/PushBack/InsertBefore/InsertAfter
return the new *list.Element, and Remove, MoveToFront, MoveToBack, MoveBefore and MoveAfter take an element and run in O(1).
An Element exposes Value any, Next() and Prev(). Because the values are any, you assert them back on reading.
The classic use is an LRU cache: a map from key to element for lookup, and a list ordered by recency, where a hit moves the element to the front and an insertion beyond the capacity evicts the back.
container/ring is a circular list: there is no head or tail, every *ring.Ring is an element and a pointer to any of them
refers to the whole ring. ring.New(n) builds a ring of n elements, Next, Prev and Move(n) walk it, Do visits every element,
Len counts them (in O(n)), and Link/Unlink splice rings together or cut elements out. A ring with a fixed size is a
natural bounded history, for example the last three searches:
package main
import (
"container/list"
"container/ring"
"fmt"
)
// lru is a least-recently-used cache of book titles by ISBN.
type lru struct {
cap int
order *list.List // front = most recently used
items map[string]*list.Element
}
type entry struct{ isbn, title string }
func newLRU(n int) *lru {
return &lru{cap: n, order: list.New(), items: map[string]*list.Element{}}
}
func (c *lru) Get(isbn string) (string, bool) {
el, ok := c.items[isbn]
if !ok {
return "", false
}
c.order.MoveToFront(el)
return el.Value.(entry).title, true
}
func (c *lru) Put(isbn, title string) {
if el, ok := c.items[isbn]; ok {
el.Value = entry{isbn, title}
c.order.MoveToFront(el)
return
}
c.items[isbn] = c.order.PushFront(entry{isbn, title})
if c.order.Len() > c.cap {
last := c.order.Back()
delete(c.items, last.Value.(entry).isbn)
c.order.Remove(last)
}
}
func (c *lru) Keys() []string {
var keys []string
for el := c.order.Front(); el != nil; el = el.Next() {
keys = append(keys, el.Value.(entry).isbn)
}
return keys
}
func main() {
c := newLRU(2)
c.Put("a", "Dune")
c.Put("b", "Emma")
c.Get("a") // a becomes most recent
c.Put("c", "Ulysses") // evicts b
_, okB := c.Get("b")
fmt.Println(c.Keys(), okB)
// container/ring: a fixed-size circular buffer of recent searches.
r := ring.New(3)
for _, q := range []string{"dune", "emma", "ulysses", "dubliners"} {
r.Value = q
r = r.Next()
}
r.Do(func(v any) { fmt.Print(v, " ") }) // oldest to newest
fmt.Println(r.Len())
// Rotate moves the starting point; Link and Unlink splice rings.
r = r.Move(1)
r.Do(func(v any) { fmt.Print(v, " ") })
fmt.Println()
}
[c a] false
emma ulysses dubliners 3
ulysses dubliners emma
Source: container/list package
Source: container/ring package
When should you use them? Rarely. Linked lists have poor cache locality and cost one allocation per element, so a slice is faster
for almost every workload; list earns its place when you need O(1) removal or reordering of an element you already hold, as in the
LRU cache. ring suits a fixed-size buffer where overwriting the oldest entry is the policy, although a slice with an index modulo its
length does the same with fewer allocations. Neither has a generic version in the standard library.
math
The math package holds the floating-point functions of the IEEE 754 world and the limits of the integer types. Everything operates
on float64 unless the name says otherwise; there is no generic version, so float32 code converts. Constants are untyped until used, so
math.Pi and the exact constant expression 0.1 + 0.2 have arbitrary precision at compile time and only become 64-bit values when
assigned to a variable (see Variables, Constants and iota).
| Group | Members |
|---|---|
Integer limits |
|
Float limits |
|
Constants |
|
Rounding |
|
Remainders |
|
Powers and roots |
|
Logarithms |
|
Trigonometry |
|
Special values |
|
Comparison |
|
Representation |
|
Special functions |
|
Fused multiply-add |
|
package main
import (
"fmt"
"math"
)
func main() {
// Constants are exact (untyped) until they are used.
fmt.Println(math.MaxInt8, math.MinInt64, uint64(math.MaxUint64))
fmt.Println(math.MaxInt, math.Pi)
fmt.Println(math.MaxFloat64, math.SmallestNonzeroFloat64)
// Rounding family.
for _, x := range []float64{2.5, -2.5, 2.4, 3.5} {
fmt.Println(x, math.Floor(x), math.Ceil(x), math.Trunc(x), math.Round(x), math.RoundToEven(x))
}
// Special values.
inf, nan := math.Inf(1), math.NaN()
fmt.Println(inf, -inf, nan, 1/inf, math.IsInf(inf, 0), math.IsNaN(nan))
fmt.Println(nan == nan, nan != nan)
var zero float64
fmt.Println(1/zero, -1/zero, zero/zero) // no panic for floats
fmt.Println(math.Sqrt(-1), math.Log(0), math.Log(-1))
// Mod keeps the sign of the dividend; Remainder is IEEE 754 round-to-nearest.
fmt.Println(math.Mod(-7, 3), math.Remainder(7, 3), -7%3)
fmt.Println(math.Hypot(3, 4), math.Cbrt(27), math.Pow(2, 10), math.Pow(-8, 1.0/3))
fmt.Println(math.Abs(-0.5), math.Max(1, nan), math.Min(math.Copysign(0, -1), 0), math.Copysign(3, -1))
// Floating-point equality: compare with a tolerance.
x, y := 0.1, 0.2 // variables: the constant expression 0.1+0.2 would be exact
a, b := x+y, 0.3
fmt.Println(a == b, math.Abs(a-b) < 1e-9, a)
// Integer overflow wraps silently; detect it before it happens.
var i int8 = math.MaxInt8
i++
fmt.Println(i)
maxv := int64(math.MaxInt64)
fmt.Println(maxv+1 < 0, addOverflows(maxv, 1))
// Frexp/Modf/Float64bits expose the representation.
frac, exp := math.Frexp(48)
ip, fp := math.Modf(3.75)
fmt.Println(frac, exp, ip, fp)
fmt.Printf("%#016x %v\n", math.Float64bits(1.0), math.Float64frombits(0x4000000000000000))
fmt.Println(math.Nextafter(1, 2) - 1)
}
// addOverflows reports whether a+b would overflow int64.
func addOverflows(a, b int64) bool {
return (b > 0 && a > math.MaxInt64-b) || (b < 0 && a < math.MinInt64-b)
}
127 -9223372036854775808 18446744073709551615
9223372036854775807 3.141592653589793
1.7976931348623157e+308 5e-324
2.5 2 3 2 3 2
-2.5 -3 -2 -2 -3 -2
2.4 2 3 2 2 2
3.5 3 4 3 4 4
+Inf -Inf NaN 0 true true
false true
+Inf -Inf NaN
NaN -Inf NaN
-1 1 -1
5 3 1024 NaN
0.5 NaN -0 -3
false true 0.30000000000000004
-128
true true
0.75 6 3 0.75
0x3ff0000000000000 2
2.220446049250313e-16
Source: math package
What the output teaches:
-
Float division by zero does not panic.
1/zerois+Inf,zero/zeroisNaN. Integer division by zero does panic. -
NaN is never equal to anything, including itself, so
x == xis false for a NaN. Usemath.IsNaN. Using a NaN as a map key creates entries that can never be found again; see Maps. -
math.Roundrounds halves away from zero (2.5 becomes 3), whileRoundToEvenrounds them to the even neighbour (2.5 becomes 2), which avoids a systematic bias.fmtuses round-half-even when formatting with%.0f. -
%versusmath.Mod: both keep the sign of the dividend (-7 % 3is -1), unlike Python;math.Remainderfollows IEEE 754 and returns 1 forRemainder(7, 3)because 7/3 rounds to 2. -
Equality of floats:
0.1 + 0.2computed from variables is not0.3. Compare with a tolerance (absolute, relative, or both) suited to the problem. The constant expression0.1 + 0.2is exactly0.3because constant arithmetic is exact; the example uses variables to show the run-time behavior. -
Integer overflow wraps silently for fixed-size integers (
int8127 plus 1 is -128). The language has no overflow trap and no checked arithmetic; detect it before it happens, asaddOverflowsdoes, or withmath/bitsfor unsigned values (below). -
Signed zero:
-0exists andmath.Min(-0, 0)returns-0;cmp.Compareand==treat both zeros as equal.
math/big
math/big provides three arbitrary-precision types:
| Type | Represents | Constructors |
|---|---|---|
|
Signed integers of any size |
|
|
Exact rationals |
|
|
Floating-point numbers with a chosen precision in bits ( |
|
The API is designed around in-place results: z.Add(x, y) sets z = x + y and returns z, so expressions chain and no allocation is
needed when z already has capacity. The receiver may alias an operand (z.Mul(z, z)). There are no operators; you cannot write
a + b for a *big.Int. The zero value of each type is a ready-to-use 0, but never copy a big.Int or big.Float by assigning the struct:
the copy shares the underlying slice, so later writes to one corrupt the other. Copy with new(big.Int).Set(x).
package main
import (
"fmt"
"math/big"
)
func main() {
// Int: arbitrary precision, methods write into the receiver.
f := big.NewInt(1)
for i := int64(2); i <= 30; i++ {
f.Mul(f, big.NewInt(i)) // f = f * i
}
fmt.Println("30! =", f, f.BitLen(), "bits")
n, ok := new(big.Int).SetString("123456789012345678901234567890", 10)
fmt.Println(n, ok)
_, ok = new(big.Int).SetString("12x", 10)
fmt.Println(ok)
a, b := big.NewInt(-7), big.NewInt(2)
q, r := new(big.Int).QuoRem(a, b, new(big.Int)) // truncated, like Go's / and %
d, m := new(big.Int).DivMod(a, b, new(big.Int)) // Euclidean: remainder >= 0
fmt.Println(q, r, d, m)
// Go 1.27: Divide with an explicit rounding mode.
for _, mode := range []big.RoundingMode{big.Trunc, big.Floor, big.Round, big.Ceil} {
q, r := new(big.Int).Divide(a, b, new(big.Int), mode)
fmt.Println(mode, q, r)
}
fmt.Println(new(big.Int).Exp(big.NewInt(2), big.NewInt(100), nil))
fmt.Println(new(big.Int).Exp(big.NewInt(3), big.NewInt(1000), big.NewInt(1_000_007))) // modular
fmt.Println(new(big.Int).GCD(nil, nil, big.NewInt(84), big.NewInt(36)))
fmt.Println(new(big.Int).ModInverse(big.NewInt(3), big.NewInt(11)))
fmt.Println(big.NewInt(1_000_003).ProbablyPrime(20), a.Sign(), a.Cmp(b), a.CmpAbs(b))
fmt.Printf("%x %s %v\n", f, f.Text(36), f.IsInt64())
// Rat: exact fractions.
x := big.NewRat(1, 3)
y := big.NewRat(1, 6)
s := new(big.Rat).Add(x, y)
fmt.Println(s, s.FloatString(5), s.Num(), s.Denom())
// Float: arbitrary precision (in bits), explicit rounding mode.
p := new(big.Float).SetPrec(200).SetInt64(2)
root := new(big.Float).SetPrec(200).Sqrt(p)
fmt.Println(root.Text('f', 50))
fmt.Println(new(big.Float).SetPrec(53).Quo(big.NewFloat(1), big.NewFloat(3)))
// The zero value is a valid 0, and a result may alias an operand.
var z big.Int
fmt.Println(z.String())
z.Add(&z, big.NewInt(5))
z.Mul(&z, &z)
fmt.Println(&z)
}
30! = 265252859812191058636308480000000 108 bits
123456789012345678901234567890 true
false
-3 -1 -4 1
ToZero -3 -1
ToNegativeInf -4 1
ToNearestEven -4 1
ToPositiveInf -3 -1
1267650600228229401496703205376
297623
12
4
true -1 -1 1
d13f6370f96865df5dd54000000 jucotkuo7qe0hs0000000 false
1/2 0.50000 1 2
1.41421356237309504880168872420969807856967187537695
0.3333333333333333
0
25
Source: math/big package
Source: Go 1.27 release notes: math/big
Observations:
-
QuoRemtruncates toward zero like Go’s/and%(-7/2 is -3, remainder -1);DivModuses Euclidean division so the remainder is never negative (-4 and 1). -
Int.Divideis new in Go 1.27. It takes an explicitRoundingMode(Trunc,Floor,RoundorCeil) and writes the quotient and the remainder together, so you no longer assemble floor or ceiling division fromQuoRemand sign fix-ups. The remainder satisfiesr = x - y*qfor the chosen rounding.Roundis the same constant asToNearestEven;Truncprints asToZero,FloorasToNegativeInfandCeilasToPositiveInf, which is why the output uses those names. -
Exp(x, y, m)with a non-nil modulus is modular exponentiation; withnilit is plainx**y.GCD,ModInverseandProbablyPrime(n)(which has a negligible false-positive chance forn = 20) are the number-theory basics. -
Ratkeeps results in lowest terms and prints1/2;FloatString(5)rounds to five decimals. Use it for money-like quantities where float rounding is unacceptable. -
A
big.Floatcreated withSetPrec(200)has 200 bits of mantissa;NewFloatgives 53 bits, which isfloat64precision. -
Formatting verbs:
%d,%x,%s,%vwork on*big.Int, which implementsfmt.Formatter; print the pointer, not the struct. -
Operations on big numbers allocate and are orders of magnitude slower than
int64; use them only when the range requires it.
Big numbers also arise for correctness: if you only need to know that a 64-bit result would overflow, math/bits does it without allocation.
math/bits
math/bits has functions on uint8, uint16, uint32, uint64 and uint (suffix 8, 16, 32, 64 or none) that the compiler
turns into single instructions on common CPUs:
| Function | Result |
|---|---|
|
Population count, zero counts at either end, and the number of bits needed ( |
|
Bit reversal and byte swap (endianness conversion) |
|
Rotation; a negative |
|
Sum or difference with carry or borrow in and out |
|
Full 128-bit product, and division of a 128-bit value by a 64-bit one |
|
32 or 64 |
package main
import (
"fmt"
"math"
"math/bits"
)
// mulChecked multiplies two uint64 values and reports overflow using bits.Mul64.
func mulChecked(a, b uint64) (uint64, bool) {
hi, lo := bits.Mul64(a, b)
return lo, hi != 0
}
// addChecked adds with carry detection.
func addChecked(a, b uint64) (uint64, bool) {
sum, carry := bits.Add64(a, b, 0)
return sum, carry != 0
}
// Set is a bitset for loan states 0..63.
type Set uint64
func (s Set) Has(i uint) bool { return s&(1<<i) != 0 }
func (s *Set) Add(i uint) { *s |= 1 << i }
func (s Set) Len() int { return bits.OnesCount64(uint64(s)) }
func main() {
x := uint32(0b0010_1100)
fmt.Println(bits.OnesCount32(x), bits.Len32(x), bits.LeadingZeros32(x), bits.TrailingZeros32(x))
fmt.Printf("%08b %08b %08b\n", bits.Reverse8(0b0000_0110), bits.RotateLeft8(0b1000_0001, 1), bits.RotateLeft8(0b1000_0001, -1))
fmt.Printf("%#x\n", bits.ReverseBytes32(0x01020304))
fmt.Println(mulChecked(1<<32, 1<<31))
fmt.Println(mulChecked(1<<32, 1<<32))
fmt.Println(addChecked(math.MaxUint64, 1))
// 128-bit division: (hi, lo) / y; panics if y <= hi.
q, r := bits.Div64(1, 0, 3) // 2^64 / 3
fmt.Println(q, r)
// Next power of two and power-of-two test.
n := uint(37)
next := uint(1) << bits.Len(n-1)
fmt.Println(next, bits.OnesCount(next) == 1, bits.UintSize)
var s Set
s.Add(3)
s.Add(40)
fmt.Println(s.Has(3), s.Has(4), s.Len(), bits.TrailingZeros64(uint64(s)))
defer func() { fmt.Println("recovered:", recover()) }()
bits.Div64(5, 0, 3) // y <= hi: quotient overflows
}
3 6 26 2
01100000 00000011 11000000
0x4030201
9223372036854775808 false
0 true
0 true
6148914691236517205 1
64 true 64
true false 2 3
recovered: runtime error: integer overflow
Source: math/bits package
Typical uses are bitsets and flag sets (OnesCount is the size, TrailingZeros the lowest member), rounding up to a power
of two (1 << bits.Len(n-1)), hash and checksum mixing with rotations, and checked unsigned arithmetic with Add64 and Mul64, where
a non-zero carry or high word means overflow. Div64 panics when the quotient does not fit (y <= hi) or when y is zero.
Pseudo-random numbers: math/rand/v2
math/rand/v2 (Go 1.22) is the current package for non-cryptographic random numbers. It is the first "v2" in the standard library and
fixes the design problems of the original math/rand.
math/rand (v1) |
math/rand/v2 |
Change |
|---|---|---|
|
|
Consistent names; unsigned variants |
none |
|
Go 1.22; also a method on |
|
none |
|
|
|
Better-defined algorithms with documented seeds |
|
none |
Deprecated; use |
|
same |
Unchanged |
|
same |
Unchanged |
The top-level functions (rand.IntN, rand.Float64, rand.Shuffle, …) use a randomly seeded generator that the runtime
provides; they are safe for concurrent use and need no setup. To get a reproducible sequence, build your own generator from a
source:
-
rand.NewPCG(seed1, seed2): a small, very fast generator with 128 bits of state; -
rand.NewChaCha8(seed): a ChaCha8-based generator with a 32-byte seed, designed to resist prediction. It is what the runtime uses for the top-level functions.
A *rand.Rand (made with rand.New(source)) is not safe for concurrent use. Give each goroutine its own, or guard it with a mutex.
package main
import (
"fmt"
"math/rand/v2"
"slices"
"time"
)
func main() {
// A reproducible generator: PCG with an explicit two-word seed.
r := rand.New(rand.NewPCG(1, 2))
fmt.Println(r.IntN(100), r.IntN(100), r.IntN(100))
fmt.Println(r.Float64())
fmt.Println(r.Perm(5))
// Same seed, same sequence.
r2 := rand.New(rand.NewPCG(1, 2))
fmt.Println(r2.IntN(100), r2.IntN(100), r2.IntN(100))
// ChaCha8 takes a 32-byte seed.
seed := [32]byte([]byte("shelf-test-seed-0123456789abcdef"))
c := rand.New(rand.NewChaCha8(seed))
fmt.Println(c.Uint64(), c.IntN(10))
// Shuffle in place.
titles := []string{"Dune", "Emma", "Ulysses", "Dubliners", "Persuasion"}
rand.New(rand.NewPCG(7, 7)).Shuffle(len(titles), func(i, j int) {
titles[i], titles[j] = titles[j], titles[i]
})
fmt.Println(titles)
// Go 1.27: N is also a method on *Rand, generic over any integer type,
// so a Duration needs no conversion.
d := r.N(10 * time.Second)
fmt.Println(d >= 0 && d < 10*time.Second)
rr := rand.New(rand.NewPCG(1, 2))
fmt.Println(rr.N(int64(1000)), rr.N(uint8(200)), rr.N(time.Hour))
// The top-level functions use a randomly seeded generator: values differ per run.
a, b := rand.IntN(1<<30), rand.IntN(1<<30)
fmt.Println(a >= 0, b >= 0, rand.N(time.Minute) < time.Minute)
// Weighted/other distributions: NormFloat64, ExpFloat64, Zipf.
z := rand.NewZipf(rand.New(rand.NewPCG(3, 4)), 1.5, 2, 100)
counts := make([]int, 4)
for range 1000 {
counts[min(z.Uint64(), 3)]++
}
fmt.Println(counts, slices.Max(counts) > 0)
defer func() { fmt.Println("recovered:", recover()) }()
rand.IntN(0)
}
76 61 78
0.4297927436037299
[2 3 4 0 1]
76 61 78
15734305148095224245 3
[Dune Emma Dubliners Ulysses Persuasion]
true
769 123 47m3.940805972s
true true true
[229 121 101 549] true
recovered: invalid argument to IntN
Source: math/rand/v2 package
Every line of this output is the same on every run: the sequences for a given seed are fixed, which is what makes seeded generators
useful for tests and simulations. The top-level functions (rand.IntN, rand.N) are randomly seeded, so the program prints only
booleans about them instead of their values.
Notes:
-
IntN(n),Int64N(n),rand.N(n)and their kin panic whenn <= 0(the last line prints the message). -
IntN(n)is uniform: the package avoids the modulo bias ofrand.Int() % n. -
Perm(n)returns a random permutation of0..n-1;Shuffle(n, swap)shuffles anything with a swap function, such as the titles above. Prefer these over hand-written Fisher-Yates loops. -
Zipfgenerates values with the Zipf distribution, used to model skewed access patterns when benchmarking caches. -
Never use
math/randfor secrets. The package documentation says explicitly that its output might be easily predictable regardless of how it is seeded, and thatChaCha8is resistant to prediction whilePCGis not suited to security work. Passwords, tokens, session identifiers and keys usecrypto/rand.
Go 1.27: N as a method, and the stdversion check
Go 1.27 added generic methods to the language (a method can have its own type parameters, though interfaces cannot). The first
standard-library use is (*Rand).N, which matches the top-level rand.N. The call rr.N(time.Hour) in the program above
returns a time.Duration directly.
The go line in go.mod selects language features; new library functions are not hidden by it, but go vet reports a call to a standard-library
symbol that is newer than the module’s go version. In a module that says go 1.26:
r := rand.New(rand.NewPCG(1, 2))
fmt.Println(r.N(10), uuid.New())
main.go:11:16: rand.(*Rand).N requires go1.27 or later (module is go1.26)
main.go:11:28: uuid.New requires go1.27 or later (module is go1.26)
The stdversion analyzer of go vet does this check, and it applies equally to Int.Divide, maphash.Hasher and the uuid package below. See
Toolchain Versions and Compatibility for the go and toolchain lines.
Misuse of the type parameter is a compile error that lists the allowed types:
var n int = 10
fmt.Println(rand.N(n), rand.IntN(int64(5)), rand.N(2.5))
./main.go:10:35: cannot use int64(5) (constant 5 of type int64) as int value in argument to rand.IntN
./main.go:10:52: float64 does not satisfy rand.intType (float64 missing in ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr)
Source: math/rand/v2: N and math/rand/v2: IntN
crypto/rand
crypto/rand reads from the operating system’s cryptographically secure generator (getrandom(2) on Linux, arc4random_buf on macOS,
ProcessPrng on Windows). It has no seed and no reproducibility; that is the point.
| Function | Use |
|---|---|
|
A random string of at least 128 bits, in the base32 alphabet ( |
|
Fills |
|
Uniform |
|
A random prime of the given bit length |
|
The shared |
package main
import (
crand "crypto/rand"
"encoding/hex"
"fmt"
"math/big"
"math/rand/v2"
"regexp"
)
func main() {
// A session or API token: use crypto/rand, never math/rand.
token := crand.Text()
fmt.Println(len(token), regexp.MustCompile(`^[A-Z2-7]+$`).MatchString(token))
// Raw random bytes; Read never returns an error (it crashes the program instead).
key := make([]byte, 16)
crand.Read(key)
fmt.Println(len(hex.EncodeToString(key)))
// A uniform integer in [0, max) of any size.
n, err := crand.Int(crand.Reader, big.NewInt(1000))
fmt.Println(n.Cmp(big.NewInt(1000)) < 0, err)
// Seed a fast, non-cryptographic generator from the OS source when you want
// per-run variation but your own generator instance (e.g. one per worker).
var seed [32]byte
crand.Read(seed[:])
local := rand.New(rand.NewChaCha8(seed))
fmt.Println(local.IntN(6)+1 >= 1)
// The ChaCha8 generator is also what the top-level math/rand/v2 functions use,
// seeded by the runtime. It is fast, but its output is predictable from its state.
}
26 true
32
true <nil>
true
Source: crypto/rand package
The program prints properties (lengths, a character-set check) rather than values because the values differ on every run.
The two packages are imported as crand and rand to avoid the name clash.
| Need | Use | Why |
|---|---|---|
Session token, API key, password reset link, salt, nonce |
|
Unpredictable even if the attacker has seen earlier outputs |
Shuffle a playlist, pick a sample, jitter a retry delay |
|
Fast, concurrency-safe, no setup |
Reproducible simulation or test data |
|
The sequence is stable across runs and releases |
Uniform integer below a big bound |
|
No modulo bias on arbitrary sizes |
Do not reduce crypto/rand bytes with % to get a number in a range: the result is biased. Use crypto/rand.Int, or rejection sampling.
The cryptographic hash, MAC and key-derivation side is on Crypto and Security.
uuid (Go 1.27)
Go 1.27 adds the package uuid for RFC 9562 universally unique identifiers. Its import path is just "uuid": like fmt or slices, it is part of the
standard library, with no domain prefix, so it needs no entry in go.mod. It was proposed in
issue 62026 and is documented at pkg.go.dev/uuid. (The popular third-party
github.com/google/uuid module is a separate package with a larger API; code that imports it keeps working.) The local toolchain’s
go doc uuid and $GOROOT/src/uuid confirm the contents described here.
Because uuid is in the standard library, the module file needs nothing but the language version:
module example.com/shelf
go 1.27
The API is deliberately small.
| Member | What it does |
|---|---|
|
A UUID is a 16-byte array: comparable with |
|
A new UUID using the algorithm "suitable for most purposes"; currently the same as |
|
Version 4: 122 random bits from a cryptographically secure source |
|
Version 7: a 48-bit Unix millisecond timestamp in the high bits, at least 62 random bits; UUIDs sort in increasing order (unless the clock moves backwards) |
|
Accepts |
|
Like |
|
The all-zero and all-ones special UUIDs of RFC 9562 sections 5.9 and 5.10; |
|
Lowercase hex-and-dash form |
|
-1, 0 or +1 in big-endian byte order (RFC 9562 section 6.11), usable directly as |
|
|
There are no constructors for versions 1, 3, 5, 6 or 8 (the name-based and time-and-MAC variants), no Version() or Variant() accessor,
no database/sql Scanner/Valuer, and no exported error value for a parse failure. Where you need those, store id.String() in the
database column (or the 16 bytes as id[:]), read the version from id[6]>>4, and treat parse errors as opaque.
package main
import (
"encoding/json"
"fmt"
"slices"
"uuid"
)
// Member is a library member identified by a UUID.
type Member struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
}
func main() {
id := uuid.New() // currently the same algorithm as NewV4
fmt.Println(len(id.String()), id[6]>>4, id[8]>>6) // version nibble and variant bits
v7a, v7b, v7c := uuid.NewV7(), uuid.NewV7(), uuid.NewV7()
fmt.Println(v7a[6]>>4, v7a.Compare(v7b), v7b.Compare(v7c))
ids := []uuid.UUID{v7c, v7a, v7b}
slices.SortFunc(ids, uuid.UUID.Compare)
fmt.Println(slices.Equal(ids, []uuid.UUID{v7a, v7b, v7c}))
// Parse accepts four forms, any letter case.
for _, s := range []string{
"f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
"{F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6}",
"urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
"f81d4fae7dec11d0a76500a0c91e6bf6",
} {
u, err := uuid.Parse(s)
fmt.Println(u, err)
}
_, err := uuid.Parse("not-a-uuid")
fmt.Println(err)
fmt.Println(uuid.Nil(), uuid.Max(), uuid.Nil() == uuid.UUID{})
// UUID implements encoding.TextMarshaler/TextUnmarshaler, so JSON uses strings.
m := Member{ID: uuid.MustParse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"), Name: "Ann"}
data, _ := json.Marshal(m)
fmt.Println(string(data))
var back Member
fmt.Println(json.Unmarshal(data, &back), back == m)
// Being an array, a UUID is comparable and can be a map key.
byID := map[uuid.UUID]Member{m.ID: m}
fmt.Println(byID[m.ID].Name)
defer func() { fmt.Println("recovered:", recover()) }()
uuid.MustParse("nope")
}
36 4 2
7 -1 -1
true
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
f81d4fae-7dec-11d0-a765-00a0c91e6bf6 <nil>
invalid uuid
00000000-0000-0000-0000-000000000000 ffffffff-ffff-ffff-ffff-ffffffffffff true
{"id":"f81d4fae-7dec-11d0-a765-00a0c91e6bf6","name":"Ann"}
<nil> true
Ann
recovered: invalid uuid
Source: uuid package
The two small numbers on the first line are the version nibble (4 for New) and the variant bits (binary 10, shown as 2); on the second line,
NewV7 returns version 7 and each call compares lower than the next (-1), which Compare confirms. The JSON round trip works because of the text-marshaling methods,
so a Member can be sent to cmd/shelfd clients as a plain string. The MustParse("nope") at the end panics with the message
invalid uuid; because the error value is unexported, test for failure with err != nil only.
Choosing between version 4 and version 7
Version 4 (New, NewV4) |
Version 7 (NewV7) |
|
|---|---|---|
Content |
122 random bits |
Millisecond timestamp (plus a sub-millisecond fraction in the current implementation) and random bits |
Ordering |
None; random |
Time-ordered, so new rows land at the end of a B-tree index |
Leaks |
Nothing |
The creation time (readable by anyone holding the ID) |
Good for |
Opaque identifiers, tokens that must not reveal when they were made |
Database primary keys, log and event identifiers, anything sorted or paginated by creation |
A random v4 key scatters inserts across the whole index of a table, which hurts write locality on large tables; v7 appends. If the creation time must not
leak, use v4. A UUID is an identifier, not a secret: a v7 UUID is partly predictable, and even a v4 should not be your only protection
for access to a resource. For security tokens use crypto/rand.Text. The program below reads the timestamp back out of a v7 value:
package main
import (
"fmt"
"time"
"uuid"
)
// createdAt extracts the millisecond timestamp stored in a version 7 UUID.
func createdAt(u uuid.UUID) (time.Time, bool) {
if u[6]>>4 != 7 {
return time.Time{}, false
}
var ms int64
for _, b := range u[:6] {
ms = ms<<8 | int64(b)
}
return time.UnixMilli(ms), true
}
func main() {
before := time.Now().Add(-time.Second)
id := uuid.NewV7()
t, ok := createdAt(id)
fmt.Println(ok, t.After(before), time.Since(t) < time.Minute)
_, ok = createdAt(uuid.NewV4())
fmt.Println(ok)
// A fixed v7 value from RFC 9562 appendix A.6 style layout.
fixed := uuid.MustParse("017f22e2-79b0-7cc3-98c4-dc0c0c07398f")
t, ok = createdAt(fixed)
fmt.Println(ok, t.UTC().Format(time.RFC3339))
}
true true true
false
true 2022-02-22T19:22:22Z
Source: RFC 9562: UUID Version 7
The fixed value in the last call is the UUIDv7 example from RFC 9562, whose first 48 bits are 1645557742000 milliseconds, which is 22 February 2022.
See encoding/json for how TextMarshaler values are encoded, and
database/sql for storing identifiers.
hash/maphash
hash/maphash hashes byte sequences and comparable values to a uint64 with a seeded, collision-resistant function. It is the building
block for your own hash tables, Bloom filters and sharding. It is not cryptographic and not stable: a Seed exists only in one process
and cannot be saved, so never store or compare hashes across runs.
-
maphash.MakeSeed()creates a randomSeed. Give each data structure its own seed; a different seed is a different hash function, which defeats hash-flooding denial-of-service attacks. -
maphash.String(seed, s)andmaphash.Bytes(seed, b)hash text and bytes in one call. -
maphash.Comparable(seed, v)(Go 1.24) hashes anycomparablevalue so thatv1 == v2implies equal hashes. -
maphash.Hashis the incremental form:SetSeed,Write,WriteString,WriteByte,Sum64,Reset, andWriteComparablefor non-byte data. It is not safe for concurrent use (aSeedis). -
Go 1.27: the interface
maphash.Hasher[T](methodsHash(*Hash, T)andEqual(x, y T) bool) is the contract between a hash-based container and its elements, andmaphash.ComparableHasher[T]is the ready-made implementation forcomparabletypes that uses==. AHashercan define an equivalence other than==: case-insensitive strings, pointers compared by their fields, even types that are not comparable.
The contract has two rules: values that are Equal must produce the same hash, and a Hasher must be stateless. The program below builds a minimal
generic hash set on top of the interface and plugs in a case-insensitive hasher:
package main
import (
"fmt"
"hash/maphash"
"strings"
)
// CaseInsensitive is a maphash.Hasher[string] that ignores letter case.
type CaseInsensitive struct{}
func (CaseInsensitive) Hash(h *maphash.Hash, s string) { h.WriteString(strings.ToLower(s)) }
func (CaseInsensitive) Equal(x, y string) bool { return strings.ToLower(x) == strings.ToLower(y) }
// HashSet is a minimal open-hashing set built on any maphash.Hasher.
type HashSet[T any] struct {
hasher maphash.Hasher[T]
seed maphash.Seed
buckets map[uint64][]T
}
func NewHashSet[T any](h maphash.Hasher[T]) *HashSet[T] {
return &HashSet[T]{hasher: h, seed: maphash.MakeSeed(), buckets: map[uint64][]T{}}
}
func (s *HashSet[T]) sum(v T) uint64 {
var h maphash.Hash
h.SetSeed(s.seed)
s.hasher.Hash(&h, v)
return h.Sum64()
}
func (s *HashSet[T]) Add(v T) bool {
k := s.sum(v)
for _, e := range s.buckets[k] {
if s.hasher.Equal(e, v) {
return false
}
}
s.buckets[k] = append(s.buckets[k], v)
return true
}
func (s *HashSet[T]) Has(v T) bool {
for _, e := range s.buckets[s.sum(v)] {
if s.hasher.Equal(e, v) {
return true
}
}
return false
}
type ISBNKey struct {
Prefix string
Number int
}
func main() {
seed := maphash.MakeSeed()
h1 := maphash.String(seed, "Dune")
h2 := maphash.Bytes(seed, []byte("Dune"))
fmt.Println(h1 == h2, h1 == maphash.String(seed, "dune"))
// Comparable hashes any comparable value consistently with ==.
k := ISBNKey{"978", 441013593}
fmt.Println(maphash.Comparable(seed, k) == maphash.Comparable(seed, ISBNKey{"978", 441013593}))
// A different seed is a different hash function (hash-flooding resistance),
// and a seed cannot be saved: the numbers differ from run to run.
fmt.Println(maphash.String(seed, "Dune") == maphash.String(maphash.MakeSeed(), "Dune"))
// Incremental hashing: chunks do not matter.
var a, b maphash.Hash
a.SetSeed(seed)
b.SetSeed(seed)
a.WriteString("Dune")
b.WriteString("Du")
b.WriteString("ne")
fmt.Println(a.Sum64() == b.Sum64())
// Hasher (Go 1.27) lets containers plug in their own equivalence.
set := NewHashSet[string](CaseInsensitive{})
fmt.Println(set.Add("Dune"), set.Add("DUNE"), set.Add("Emma"), set.Has("emma"), set.Has("Ulysses"))
keys := NewHashSet[ISBNKey](maphash.ComparableHasher[ISBNKey]{})
fmt.Println(keys.Add(k), keys.Add(k), keys.Has(ISBNKey{"978", 1}))
}
true false
true
false
true
true false true true false
true false false
Source: hash/maphash package
The first four lines show the basic guarantees: the string and bytes forms agree, hashing is case-sensitive, a different seed gives a different result, and chunked writes hash like one write.
HashSet.Add returns false for "DUNE" because the hasher treats it as equal to "Dune". The built-in map cannot do this: it always uses == and
comparable keys. A generic standard-library hash table that accepts a Hasher does not exist yet; the interface is the extension point
that later containers and third-party modules are expected to share.
unique
unique.Make(v) returns a unique.Handle[T] for a comparable value v. Two handles are equal if and only if the values are equal, and comparing handles is a
pointer comparison, not a comparison of the (possibly long) values. h.Value() returns a copy of the original. This is interning (canonicalizing): many equal strings or structs share one
stored copy. The unique values are kept in a global table that does not keep them alive when no handle refers to them anymore.
package main
import (
"fmt"
"strings"
"unique"
"unsafe"
)
// Genre is interned: copies are one pointer-sized handle, and comparing two
// genres is a pointer comparison instead of a string comparison.
type Genre = unique.Handle[string]
type Book struct {
Title string
Genre Genre
}
func main() {
genres := []string{"Science Fiction", "science fiction", "Novel"}
var books []Book
for i, g := range genres {
books = append(books, Book{Title: fmt.Sprint("book", i), Genre: unique.Make(g)})
}
books = append(books, Book{Title: "book3", Genre: unique.Make(strings.Clone("Science Fiction"))})
fmt.Println(books[0].Genre == books[1].Genre) // different strings, different handles
fmt.Println(books[0].Genre == books[3].Genre) // same value, same handle
fmt.Println(books[0].Genre.Value(), unsafe.Sizeof(books[0].Genre))
}
false
true
Science Fiction 8
Source: unique package
Source: The Go Blog: New unique package
A handle is a single pointer (8 bytes on 64-bit platforms) regardless of the size of T, so a field like Genre costs less than a string header (16 bytes) and
compares in one instruction. The trade-off is a map lookup per Make, so interning pays off for values that repeat a lot and are compared often (genres, host names, log keys,
net/netip for IPv6 zone names). For values that are mostly distinct, it only adds cost. How the cleanup of unreferenced entries
works, and the sibling packages weak and runtime.AddCleanup, are on
Cleanups, Weak Pointers and Interning.
Where the books are outdated or wrong
-
Bodner (Learning Go, chapter 3) predates the
slices,mapsandcmppackages, so the hand-written loops for copying, comparing, deleting and ordered keys, and thesort.Sliceidiom, have a one-line replacement today (slices.Clone,slices.Equal,slices.Sort,slices.Sorted(maps.Keys(m))). Its chapter-15 discussion of what generics would bring is answered by these packages and byiter.Seq(see Iterators and Range-over-Func). -
Both books describe the
math/randdefault generator as having a fixed seed that you must replace withrand.Seed. Since Go 1.20 the default generator is seeded randomly,rand.Seedhas been deprecated since then and has been a no-op since Go 1.24 (unlessGODEBUG=randseednop=0), and new code usesmath/rand/v2(Go 1.22):rand.IntN,rand.N, and for reproducibility a localrand.New(rand.NewPCG(a, b)). For secrets, the books' mention ofcrypto/randstands, andrand.Text(Go 1.24) now covers token generation in one call. -
Vivien (Learning Go Programming) predates
math.FMA, the genericslices,mapsandcmphelpers,math/rand/v2and, inmath/big, theInt.Dividerounding-mode method (1.27). The width ofintis not 32 bits on 64-bit platforms (it has been 64 bits there since Go 1.1);math.MaxIntandbits.UintSizereport the real size. -
Neither book covers
unique(1.23),maphash.Comparable(1.24), themaphash.Hasherinterface (1.27) or the newuuidpackage (1.27). Before Go 1.27 the usual answer for UUIDs was a third-party module; code that still imports one keeps working, and migrating is a type change because the twoUUIDtypes are unrelated arrays of 16 bytes. -
Code that sorts a plain slice with a hand-written
sort.Interfacetype, or withsort.Slice, is superseded byslices.SortFuncwithcmp.Compare.
Version notes
| Go release | Change |
|---|---|
1.19 |
|
1.20 |
|
1.21 |
|
1.22 |
|
1.23 |
Iterator functions in |
1.24 |
|
1.25 |
|
1.27 |
|
Source: Go 1.22 release notes
Source: Go 1.24 release notes
Source: Go 1.27 release notes