h3-go ships two implementations of Uber’s H3 hexagonal index. github.com/uber/h3-go/v4 calls the vendored C library through cgo, and x/h3go is the pure Go port a cgo free build actually runs. From 28 September through 1 October 2026, master took 14 commits and 13,065 insertions across 104 files, most of them a suite that pins x/h3go to H3 4.5.0, plus measured cuts to grid disk and polygon fill time.
A conformance suite pinned to H3 4.5.0 ¶
The tree in the conformance README is format h3-conformance/1. The format commit adds the spec, a Go runner, and a C generator. Each record is one JSON line, every key present, errors as H3Error names. format.go rejects a file whose hash or record count no longer matches manifest.json. The manifest names H3 4.5.0, seed 20260929, angular tolerance 1e-11 degrees, and relative tolerance 1e-9.
gen.c is standalone C99 against the public H3 API. generate.sh compiles it with the vendored C sources, so expected values are the reference answers, not the Go binding’s. go test ./x/h3go/conformance/... stays pure Go. The JSONL is checked in. go generate ./x/h3go/conformance rewrites it after an H3 bump.
The manifest holds 1,885 inspection rows, 781 hierarchy rows, 542 each for traversal, edges, and vertexes, 1,084 local IJ rows, 1,344 set rows, 360 region rows, and digest files of 7 plus 854 lines.
The regions commit caught a real disagreement. Unscaled cell boundaries failed 240 of 270 rows in full and overlapping. The C reference also disagreed with its own output on 135 rows, because finer vertices sit on the polygon edges and rounding decides the cell. Scaling each loop by 0.8 or 1.2 clears those edges. The C library, the cgo binding, and x/h3go then agree on the 360 checked in rows, four shapes per subject, including a resolution 16 case for E_RES_DOMAIN.
The digests commit covers all 16,838,852 cells at resolutions 0 through 6 as twelve SHA-256 text streams, once per resolution and again per base cell. Order is base cell, then digit, so a bad resolution hash names one of 122 subtrees, and a failing port can diff the text. The runner stops at resolution 3 unless H3_CONFORMANCE_DIGESTS_MAXRES=6. x/h3go matches every digest. The README no longer cites an upstream pull request.
ConstructCell and GridDiskUnsafe on both packages ¶
The binding commit adds the last two h3api.h exports still missing from Go, other than deliberate omissions. Both the cgo package and x/h3go get them.
ConstructCell takes a resolution, a base cell number, and digits from 0 to 6. Extra digits are ignored. A short slice is padded with the center digit, the same zero padded array the C tests pass. A deleted pentagon digit returns ErrDeletedDigit.
GridDiskUnsafe forwards to gridDiskDistancesUnsafe in C. It adds no new walk. GridDisk hides pentagon distortion by falling back to the safe path and still returns cells. GridDiskUnsafe returns ErrPentagon instead. A job that must succeed on all 12 pentagons cannot swap the name.
Open addressing on the disk and the polygon fill ¶
Speed work this week is in x/h3go only. The cgo import still spends the time in C. Figures are benchstat medians from the commit messages, Go side, Apple M3 Max.
The safe grid disk change keeps the queue BFS and drops the visited Go map. grid.go uses a flat slice, linear probing, a zero Cell as the empty slot, and each finished ring as the next queue. GridDiskDistancesSafe went from 8.905 us to 3.858 us, and from 20 allocations to 4.
PolygonToCells was the last map backed walk, and the place Go still trailed cgo. cellset.go appends cells in discovery order. Projecting a pentagon on every size estimate was about 60 percent of allocations, so bbox.go fills pentagonRadiiKm once at init. Time went from 164.4 us to 139.0 us, memory from 33.69 KiB to 25.20 KiB, allocations from 98 to 15. Go was 15 percent slower than cgo and is now 2 percent faster.
CellsToMultiPolygon turns the set into flatTable. The edge index is a flatTable[DirectedEdge, int]. boundary.go and directededge.go append into a caller buffer. Allocations fell from 188 to 8, and time from 46.25 us to 33.18 us. Go is about 10 percent behind cgo here, 33.2 us against 30.1 us, after being about 50 percent behind. DirectedEdge.Boundary in that table dropped from 880.7 ns to 325.5 ns.
The benchmark sink, then the small calls ¶
The sink commit explains the nanosecond noise on 28 September. bench_test.go stored results in an any and boxed them. The box was a third or more of the smallest calls, and 85 of 108 Go rows showed one fake allocation. A generic noinline sink dropped the Go geomean from 94.47 ns to 68.52 ns with no library change. Polygon time barely moved, 109.2 us to 110.6 us, so the disk and fill cuts stand.
The parent and text commit ran after the sink fix. Parent ORs one mask instead of writing unused digits one resolution at a time, 5.824 ns to 2.265 ns. Cell.MarshalText went from 20.23 ns to 10.33 ns by appending one right sized slice. Callers pay one allocation, not two. IndexFromString went from 26.58 ns to 16.34 ns. Bad text still returns 0, overflow still returns math.MaxUint64, and UnmarshalText parses []byte without copying to a string. A digest over 62,048 cells is bit identical.
coordIJK value receivers predate the sink fix, so both sides still include the box. GridDistance fell from 130.45 ns to 40.02 ns, and the table geomean fell 26 percent. Normalize runs once after the loop, not once per resolution.
Cell area reuses sin and cos per vertex and hoists face tables in faceijk.go. CellAreaRads2 fell from 505.2 ns to 440.5 ns. A digest of areas and boundaries over 62,048 cells is bit identical. EdgeLength barely changed. Go was already about 30 percent ahead of cgo there.
What to watch ¶
Set H3_CONFORMANCE_DIGESTS_MAXRES=6 for the full grid through resolution 6. The default stop at resolution 3 keeps a race detector run short, and resolution 6 takes minutes.
Keep GridDisk where a pipeline must return cells near a pentagon. GridDiskUnsafe returns ErrPentagon instead of falling back. vertexToCell is still missing in both packages, so the vertex files do not check it.
A polygon that shares edges with cells is unstable in full and overlapping, in C and in Go. The regions group scales loops by 0.8 and 1.2 for that reason. Mixed resolution compactCells input is order dependent and unsampled. The JSONL is a bootstrap until gen.c moves upstream. Until then the expected values come from this repo’s build of the vendored C sources.