hashstructure

A Go library for generating deterministic hash values from arbitrary structs, maps, and slices.

Library
Go
vv1.1.0
764stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
35/100Needs Attention
Development Activity0
Maintenance0
Community52
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture78
Code Quality75
Innovation82
Learning Curve55

hashstructure is a small, dependency-free Go library that computes a deterministic uint64 hash for any Go value — including deeply nested structs, maps, slices, and arrays — using reflection to walk the value’s structure rather than requiring per-type marshaling code. Two values that are structurally equal always hash the same way regardless of map key ordering or how the value was constructed, which makes the library useful anywhere code needs to compare, cache, or deduplicate complex values without a full equality check or custom serialization.

Hashing behavior is customizable through struct tags (hash:"ignore", hash:"-", hash:"set", hash:"string") and through the Includable, IncludableMap, and Hashable interfaces, which let a type opt fields in or out of the hash, or override the hash computation entirely. The library ships two format versions — FormatV1, the original algorithm, and FormatV2, which fixes known hash-collision issues from v1 — so callers explicitly choose which collision-safety guarantees they want rather than having behavior change silently between releases.

What You Get

  • A single Hash(v, format, opts) entry point that works on any Go value through reflection — no per-type boilerplate to write or maintain
  • Struct tags (hash:"ignore", hash:"-", hash:"set", hash:"string") for declarative, field-level control over what gets hashed and how
  • HashOptions for swapping in a custom hash.Hash64 implementation, changing the struct tag name, and controlling nil/zero-value and zero-field handling
  • Includable, IncludableMap, and Hashable interfaces so a type can dynamically decide per-field or per-entry inclusion, or override its hash computation entirely

Common Use Cases

  • Detecting whether a config or resource struct has changed between reconciliation passes in an operator or controller loop
  • Building cache keys for arbitrary in-memory values without hand-writing custom serialization
  • Fingerprinting resource configuration blocks to detect drift, the pattern hashstructure was originally built for at HashiCorp
  • Comparing unordered collections — slices tagged hash:"set" — for equality regardless of element order

Under The Hood

Architecture hashstructure is organized as a single focused package split across three files: hashstructure.go holds the public Hash entry point and the internal walker that recursively traverses any Go value via reflect.Value, include.go defines the Includable/IncludableMap/Hashable extension interfaces, and errors.go isolates the two typed error values. The walker dispatches purely on reflect.Kind — numeric types are binary-written directly to a hash.Hash64, while structs, maps, slices, and arrays recurse into visit() and combine child hashes with either an ordered (hashUpdateOrdered) or XOR-based unordered (hashUpdateUnordered/hashFinishUnordered) combinator depending on whether ordering should affect the result. There’s no external state or dependency injection; the entire library is a pure function of (value, format, options), which keeps it trivially embeddable in any caller’s code path with no side effects to reason about.

Tech Stack The library has zero third-party dependencies — go.mod declares only the module path (github.com/mitchellh/hashstructure/v2) and a go 1.14 minimum, and every import (encoding/binary, fmt, hash, hash/fnv, reflect, time) comes from the standard library. Hashing defaults to FNV-64 (hash/fnv) but is fully swappable via HashOptions.Hasher for any hash.Hash64 implementation. CI (.github/workflows) runs a single go test ./… job on Go 1.15/ubuntu-latest — no build matrix, no lint step, no release automation beyond git tags.

Code Quality Test coverage is extensive relative to the library’s surface area: hashstructure_test.go exercises identity stability across 100 repeated hashes per case, cross-type equality/inequality, the ignore/set/string struct tags, nil-vs-zero-value handling, the Includable/IncludableMap/Hashable override interfaces, and explicit stringer-tag error cases, and hashstructure_examples_test.go provides runnable godoc examples with checked output. Error handling is explicit and typed (ErrNotStringer, ErrFormat implement the error interface rather than relying on bare fmt.Errorf, aside from one default-branch fallback). Naming is idiomatic Go throughout, exported identifiers carry doc comments, and there’s no evidence of a linter/formatter step in CI beyond implicit gofmt conventions.

API Design The public surface is a single function, Hash(v interface{}, format Format, opts *HashOptions), that works with zero configuration — passing nil for opts is valid and defaults to FNV-64 with a “hash” tag name. Customization is layered on through struct tags (hash:“ignore”, hash:”-”, hash:“set”, hash:“string”) that mirror the familiar encoding/json tag idiom, so Go developers already know the mental model. Deeper control is opt-in via three small interfaces (Includable, IncludableMap, Hashable) rather than required boilerplate, and the required FormatV1/FormatV2 parameter forces callers to make an explicit, documented choice about collision-safety semantics instead of silently changing hash output across versions.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search