deephash
packageThis package is not in the latest version of its module.
Details
Validgo.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used, modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
- Learn more about best practices
Repository
Links
Documentation¶
Overview¶
Package deephash hashes a Go value recursively, in a predictable order,without looping. The hash is only valid within the lifetime of a program.Users should not store the hash on disk or send it over the network.The hash is sufficiently strong and unique such thatHash(&x) == Hash(&y) is an appropriate replacement for x == y.
The definition of equality is identical to reflect.DeepEqual except:
- Floating-point values are compared based on the raw bits,which means that NaNs (with the same bit pattern) are treated as equal.
- time.Time are compared based on whether they are the same instant in timeand also in the same zone offset. Monotonic measurements and zone namesare ignored as part of the hash.
- netip.Addr are compared based on a shallow comparison of the struct.
WARNING: This package, like most of the tailscale.com Go module,should be considered Tailscale-internal; we make no API promises.
Cycle detection¶
This package correctly handles cycles in the value graph,but in a way that is potentially pathological in some situations.
The algorithm for cycle detection operates bypushing a pointer onto a stack whenever deephash is visiting a pointer andpopping the pointer from the stack after deephash is leaving the pointer.Before visiting a new pointer, deephash checks whether it has already beenvisited on the pointer stack. If so, it hashes the index of the pointeron the stack and avoids visiting the pointer.
This algorithm is guaranteed to detect cycles, but may expand pointersmore often than a potential alternate algorithm that remembers all pointersever visited in a map. The current algorithm uses O(D) memory, where Dis the maximum depth of the recursion, while the alternate algorithmwould use O(P) memory where P is all pointers ever seen, which can be a lot,and most of which may have nothing to do with cycles.Also, the alternate algorithm has to deal with challenges of producingdeterministic results when pointers are visited in non-deterministic wayssuch as when iterating through a Go map. The stack-based algorithm avoidsthis challenge since the stack is always deterministic regardless ofnon-deterministic iteration order of Go maps.
To concretely see how this algorithm can be pathological,consider the following data structure:
var big *Item = ... // some large data structure that is slow to hashvar manyBig []*Itemfor i := range 1000 {manyBig = append(manyBig, &big)}deephash.Hash(manyBig)Here, the manyBig data structure is not even cyclic.We have the same big *Item being stored multiple times in a []*Item.When deephash hashes []*Item, it hashes each individual *Itemnot realizing that it had just done the computation earlier.To avoid the pathological situation, Item should implementSelfHasher andmemoize attempts to hash itself.
Index¶
Constants¶
This section is empty.
Variables¶
This section is empty.
Functions¶
funcHasherForType¶added inv1.30.0
HasherForType returns a hash that is specialized for the provided type.
HasherForType panics if the opts are invalid for the provided type.
Currently, at most one option can be provided (IncludeFields orExcludeFields) and its type must match the type of T. Those restrictions maybe removed in the future, along with documentation about their precedencewhen combined.
Types¶
typeHasher¶added inv1.60.0
type Hasher struct {// contains filtered or unexported fields}Hasher is a value passed to [SelfHasher.Hash] that allow implementationsto hash themselves in a structured manner.
func (Hasher)HashBytes¶added inv1.60.0
HashBytes hashes a sequence of bytes b.The length of b is not explicitly hashed.
func (Hasher)HashString¶added inv1.60.0
HashString hashes the string data of sThe length of s is not explicitly hashed.
func (Hasher)HashUint16¶added inv1.60.0
HashUint16 hashes a uint16.
func (Hasher)HashUint32¶added inv1.60.0
HashUint32 hashes a uint32.
func (Hasher)HashUint64¶added inv1.60.0
HashUint64 hashes a uint64.
typeOption¶added inv1.50.0
type Option interface {// contains filtered or unexported methods}Option is an optional argument to HasherForType.
funcExcludeFields¶added inv1.50.0
ExcludeFields returns an option that modifies the hashing for T to includeall struct fields of T except those provided in fields.
T must be a struct type, and must match the type of the value passed toHasherForType.
funcIncludeFields¶added inv1.50.0
IncludeFields returns an option that modifies the hashing for T to onlyinclude the named struct fields.
T must be a struct type, and must match the type of the value passed toHasherForType.
typeSelfHasher¶added inv1.60.0
type SelfHasher interface {Hash(Hasher)}SelfHasher is implemented by types that can compute their own hashby writing values through the providedHasher parameter.Implementations must not leak the providedHasher.
If the implementation of SelfHasher recursively callsdeephash.Hash,then infinite recursion is quite likely to occur.To avoid this, use a type definition to drop methods before callingdeephash.Hash:
func (v *MyType) Hash(h deephash.Hasher) {v.hashMu.Lock()defer v.hashMu.Unlock()if v.dirtyHash {type MyTypeWithoutMethods MyType // type define MyType to drop Hash methodv.dirtyHash = false // clear out dirty bit to avoid hashing over itv.hashSum = deephash.Sum{} // clear out hashSum to avoid hashing over itv.hashSum = deephash.Hash((*MyTypeWithoutMethods)(v))}h.HashSum(v.hashSum)}In the above example, we acquire a lock since it is possible that deephashis called in a concurrent manner, which implies that MyType.Hash may alsobe called in a concurrent manner. Whether this lock is necessary isapplication-dependent and left as an exercise to the reader.Also, the example assumes that dirtyHash is set elsewhere by applicationlogic whenever a mutation is made to MyType that would alter the hash.