hashy

package
v0.1.38 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: GPL-3.0 Imports: 16 Imported by: 0

README

hashy

hashy is a powerful, deterministic hashing library for Go that generates consistent hash values for any Go data structure. It supports primitives, structs, slices, maps, and complex nested types with configurable behavior through struct tags and options.

Overview

The hashy package provides a comprehensive solution for generating deterministic hash values from Go data structures. Unlike built-in hash functions that work only on basic types, hashy can hash entire structs, nested data, and collections while respecting field ordering and custom hashing logic.

Key Features:

  • 🔐 Deterministic hashing - identical values always produce identical hashes
  • 🎯 Deep hashing - works with nested structs, slices, maps, and pointers
  • 🏷️ Struct tag support - control hashing behavior via hash:"..." tags
  • ⚙️ Highly configurable - customize behavior with fluent option builders
  • 🔄 Multiple output formats - uint64, hex, base64, SHA-256, and more
  • 🎨 Custom hash interfaces - implement Hashable for custom types
  • Optimized performance - uses FNV-1a by default with fast paths
  • 🧩 Order-independent hashing - treat slices as sets when needed
  • 🔍 Field filtering - selectively include/exclude fields via interfaces

Built on FNV-1a: By default, hashy uses the fast FNV-1a hash algorithm, known for excellent distribution and performance.

Use Cases

When to Use
  • Caching keys - generate stable cache keys from complex objects
  • Data deduplication - detect duplicate records in databases
  • Change detection - track whether data has been modified
  • Distributed systems - consistent hashing for sharding/partitioning
  • Testing - verify object equality in unit tests
  • ETags - generate HTTP ETags for API responses
  • Versioning - create version fingerprints for configuration
  • Comparison - fast equality checks for large data structures
When Not to Use
  • Cryptographic security - use crypto/* packages instead (hashy is not cryptographically secure)
  • Password hashing - use bcrypt, argon2, or similar
  • Message authentication - use HMAC instead
  • Digital signatures - use proper cryptographic signing
  • When uniqueness is critical - hash collisions are possible (though rare)

Installation

go get github.com/polarixa/replify

Import the package in your Go code:

import "github.com/polarixa/replify/pkg/hashy"

Usage

Basic Hashing

The simplest way to hash values:

package main

import (
    "fmt"
    "github.com/polarixa/replify/pkg/hashy"
)

func main() {
    // Hash a single value
    hash, err := hashy.Hash("hello world")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Hash: %d\n", hash)

    // Hash multiple values (hashed as a tuple)
    hash, err = hashy.Hash("user", 12345, true)
    fmt.Printf("Multi-value hash: %d\n", hash)

    // Hash a struct
    type User struct {
        Name string
        Age  int
    }
    user := User{Name: "Alice", Age: 30}
    hash, err = hashy.Hash(user)
    fmt.Printf("Struct hash: %d\n", hash)
}
String Hash Formats

Generate hashes in various string formats:

// SHA-256 hash string
hash256, _ := hashy.Hash256("data")
fmt.Println(hash256) // "a1b2c3d4..."

// Hexadecimal (16-character, zero-padded)
hexHash, _ := hashy.Hash16Padded("data")
fmt.Println(hexHash) // "000000000a1b2c3d"

// Hexadecimal (short, no padding)
hexShort, _ := hashy.HashHex16("data")
fmt.Println(hexShort) // "a1b2c3d"

// Base64 encoded
encoded, _ := hashy.Hash64("data")
fmt.Println(encoded)

// Decimal string
decimal, _ := hashy.Hash10("data")
fmt.Println(decimal) // "12345678901234567"

// Hexadecimal string (lowercase)
hex16, _ := hashy.Hash16("data")
fmt.Println(hex16) // "abc123def456"

// Base32 string
base32, _ := hashy.Hash32("data")
fmt.Println(base32)
Struct Tags

Control hashing behavior using struct tags:

type User struct {
    ID       int    `hash:"ignore"` // Exclude from hash
    Name     string                  // Included by default
    Password string `hash:"-"`       // Same as "ignore"
    Roles    []string `hash:"set"`   // Order-independent
    Internal string  `hash:"string"` // Use fmt.Stringer if available
}

user := User{
    ID:       123,
    Name:     "Alice",
    Password: "secret",
    Roles:    []string{"admin", "user"},
}

hash, _ := hashy.Hash(user)
// ID and Password are excluded from the hash
// Roles are hashed order-independently

Available Tags:

  • hash:"ignore" or hash:"-" - Skip this field
  • hash:"set" - Treat slice as order-independent set
  • hash:"string" - Use fmt.Stringer if type implements it

Examples

1. Caching with Hash Keys
type Product struct {
    ID          int
    Name        string
    Price       float64
    UpdatedAt   time.Time `hash:"ignore"` // Don't invalidate cache on timestamp change
}

func getCacheKey(product Product) (string, error) {
    // Generate a stable cache key
    return hashy.Hash256(product)
}

func main() {
    product := Product{
        ID:    101,
        Name:  "Laptop",
        Price: 999.99,
    }

    cacheKey, _ := getCacheKey(product)
    fmt.Println("Cache key:", cacheKey)

    // Same product = same key (even with different timestamp)
    product.UpdatedAt = time.Now()
    sameKey, _ := getCacheKey(product)
    fmt.Println("Keys match:", cacheKey == sameKey) // true
}
2. Detecting Changes
type Config struct {
    Host     string
    Port     int
    Features map[string]bool
}

func hasConfigChanged(old, new Config) bool {
    oldHash, _ := hashy.Hash(old)
    newHash, _ := hashy.Hash(new)
    return oldHash != newHash
}

func main() {
    v1 := Config{Host: "localhost", Port: 8080}
    v2 := Config{Host: "localhost", Port: 8080}
    v3 := Config{Host: "localhost", Port: 9090}

    fmt.Println("v1 vs v2:", hasConfigChanged(v1, v2)) // false
    fmt.Println("v1 vs v3:", hasConfigChanged(v1, v3)) // true
}
3. Order-Independent Slice Hashing
type Team struct {
    Name    string
    Members []string `hash:"set"` // Order doesn't matter
}

func main() {
    team1 := Team{
        Name:    "DevOps",
        Members: []string{"Alice", "Bob", "Charlie"},
    }

    team2 := Team{
        Name:    "DevOps",
        Members: []string{"Charlie", "Alice", "Bob"}, // Different order
    }

    hash1, _ := hashy.Hash(team1)
    hash2, _ := hashy.Hash(team2)

    fmt.Println("Hashes match:", hash1 == hash2) // true - order ignored
}
4. Custom Hash Options
import "hash/fnv"

func main() {
    // Create custom options
    opts := hashy.NewOptions().
        WithTagName("json").           // Use "json" tag instead of "hash"
        WithIgnoreZeroValue(true).     // Skip zero-value fields
        WithZeroNil(true).             // Treat nil as zero value
        WithSlicesAsSets(true).        // All slices are order-independent
        WithUseStringer(true).         // Always use String() method if available
        Build()

    type User struct {
        Name  string `json:"name"`
        Email string `json:"email"`
        Age   int    `json:"age"`
    }

    user := User{Name: "Alice", Email: "alice@example.com", Age: 0}

    // Hash with custom options
    hash, err := hashy.Hash(user, opts)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Custom hash: %d\n", hash)
}
5. Implementing Custom Hashable
type Coordinate struct {
    Lat float64
    Lon float64
}

// Implement Hashable interface for custom hashing logic
func (c Coordinate) Hash() (uint64, error) {
    // Round to 6 decimal places for hashing
    lat := int64(c.Lat * 1000000)
    lon := int64(c.Lon * 1000000)
    return hashy.Hash(lat, lon)
}

func main() {
    coord1 := Coordinate{Lat: 40.712776, Lon: -74.005974}
    coord2 := Coordinate{Lat: 40.7127760001, Lon: -74.0059740001} // Slightly different

    hash1, _ := hashy.Hash(coord1)
    hash2, _ := hashy.Hash(coord2)

    // Hashes match due to rounding in custom Hash() method
    fmt.Println("Hashes match:", hash1 == hash2)
}
6. Field Selection with Interfaces
type UserProfile struct {
    Username    string
    Email       string
    LastLogin   time.Time
    PrivateData string
}

// Implement FieldSelector to control which fields are hashed
func (u UserProfile) SelectField() hashy.SelectField {
    return func(field string, value any) (bool, error) {
        // Exclude sensitive and volatile fields
        excluded := []string{"PrivateData", "LastLogin"}
        for _, f := range excluded {
            if field == f {
                return false, nil
            }
        }
        return true, nil
    }
}

func main() {
    profile := UserProfile{
        Username:    "alice",
        Email:       "alice@example.com",
        LastLogin:   time.Now(),
        PrivateData: "secret",
    }

    hash, _ := hashy.Hash(profile)
    // Only Username and Email are included in the hash
    fmt.Printf("Selective hash: %d\n", hash)
}
7. Map Entry Selection
type Settings struct {
    Values map[string]string
}

// Control which map entries are hashed
func (s Settings) SelectMapEntry() hashy.SelectMapEntry {
    return func(field string, k, v any) (bool, error) {
        key, ok := k.(string)
        if !ok {
            return true, nil
        }
        // Exclude temporary settings
        return !strings.HasPrefix(key, "temp_"), nil
    }
}

func main() {
    settings := Settings{
        Values: map[string]string{
            "host":      "localhost",
            "port":      "8080",
            "temp_flag": "true", // Excluded from hash
        },
    }

    hash, _ := hashy.Hash(settings)
    fmt.Printf("Filtered map hash: %d\n", hash)
}
8. ETags for HTTP APIs
type APIResponse struct {
    Data      interface{}
    Timestamp time.Time `hash:"ignore"` // Don't include in ETag
}

func generateETag(response APIResponse) (string, error) {
    hash, err := hashy.Hash16Padded(response)
    if err != nil {
        return "", err
    }
    return `"` + hash + `"`, nil // Wrap in quotes for HTTP ETag
}

func main() {
    response := APIResponse{
        Data:      map[string]string{"status": "ok"},
        Timestamp: time.Now(),
    }

    etag, _ := generateETag(response)
    fmt.Println("ETag:", etag) // "0000000012abc34d"
}

API Reference

Core Functions
Function Description Return Type
Hash(data ...any) Generate 64-bit hash uint64, error
HashValue(value any, opts) Hash single value with options uint64, error
Hash256(data ...any) Generate SHA-256 hash string string, error
Hash16Padded(data ...any) 16-char hex hash (zero-padded) string, error
HashHex16(data ...any) Hex hash (no padding) string, error
Hash10(data ...any) Decimal string hash string, error
Hash16(data ...any) Hexadecimal string string, error
Hash32(data ...any) Base32 string string, error
Hash64(data ...any) Base64 encoded hash string, error
Options Builder
opts := hashy.NewOptions().
    WithHasher(customHasher).      // Single hash.Hash64 — not safe to share across goroutines
    WithHasherFunc(fnv.New64a).    // Factory function — safe to share across goroutines (preferred)
    WithTagName("json").            // Use different struct tag
    WithZeroNil(true).              // Treat nil pointers as zero values
    WithIgnoreZeroValue(true).      // Skip zero-value fields
    WithSlicesAsSets(true).         // All slices are order-independent
    WithUseStringer(true).          // Use fmt.Stringer when available
    Build()
Interfaces

Hashable - Custom hash implementation:

type Hashable interface {
    Hash() (uint64, error)
}

FieldSelector - Control field inclusion:

type FieldSelector interface {
    SelectField() SelectField
}

type SelectField func(field string, value any) (bool, error)

MapSelector - Control map entry inclusion:

type MapSelector interface {
    SelectMapEntry() SelectMapEntry
}

type SelectMapEntry func(field string, k, v any) (bool, error)
Struct Tags
Tag Description Example
hash:"ignore" Exclude field from hash ID int \hash:"ignore"``
hash:"-" Same as "ignore" Password string \hash:"-"``
hash:"set" Order-independent slice Tags []string \hash:"set"``
hash:"string" Use fmt.Stringer Status Status \hash:"string"``

Best Practices & Notes

⚠️ Common Pitfalls
  1. Hash Collisions: While rare with 64-bit hashes, collisions are possible. Don't rely on uniqueness for security-critical operations.

  2. Non-Deterministic Types: Be careful with:

    • map iteration order (handled automatically by hashy)
    • Pointer addresses (use values, not pointers)
    • Random values (exclude or use fixed seeds)
  3. Float Precision: Floating-point values are hashed as-is. Small differences will produce different hashes:

    hashy.Hash(0.1 + 0.2) != hashy.Hash(0.3) // May differ due to float precision
    
  4. Time Values: time.Time includes location and monotonic clock. Strip unnecessary precision:

    timestamp := time.Now().UTC().Truncate(time.Second)
    
  5. Unexported Fields: Only exported (public) struct fields are hashed.

💡 Recommendations

Use struct tags to exclude volatile fields (timestamps, IDs)

Use hash:"set" for unordered collections (permissions, tags)

Implement Hashable for types requiring custom logic

Use FieldSelector for complex inclusion rules

Generate string hashes for cache keys and ETags (Hash256, Hash16Padded)

Test hash stability across versions if persisting hashes

Document hash assumptions in struct comments

🔒 Thread Safety

Hash/HashValue called with nil options are always safe for concurrent use — DefaultOptions() creates a fresh hash.Hash64 instance for every call.

When reusing a pre-built *hashOptions across goroutines, use WithHasherFunc (a factory function) instead of WithHasher (a single instance). A shared hash.Hash64 instance carries internal mutable state, so concurrent calls that share it produce a data race.

// ✅ Safe - each call gets its own hasher via the factory
opts := hashy.NewOptions().WithHasherFunc(fnv.New64a).Build()

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(val int) {
        defer wg.Done()
        hashy.Hash(val, opts)
    }(i)
}
wg.Wait()

// ✅ Also safe - nil options always use DefaultOptions (fresh hasher per call)
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(val int) {
        defer wg.Done()
        hashy.Hash(val) // nil opts
    }(i)
}
wg.Wait()

// ❌ NOT safe - sharing a single hash.Hash64 instance causes a data race
shared := fnv.New64a()
opts2 := hashy.NewOptions().WithHasher(shared).Build()
for i := 0; i < 10; i++ {
    go func(val int) { hashy.Hash(val, opts2) }(i) // DATA RACE
}
⚡ Performance Tips
  • Reuse options: Build once, pass to multiple HashValue() calls
  • Use appropriate hash function: FNV-1a is fast; use custom hasher if needed
  • Minimize allocations: Hash values directly instead of serializing first
  • Cache hash results: For immutable data, compute once and store
  • Use uint64 format: Faster than string conversions
🐛 Debugging Hash Mismatches

If two objects that should hash the same don't:

  1. Check field order: Struct field order matters
  2. Verify zero values: Use WithIgnoreZeroValue(true) consistently
  3. Inspect tags: Ensure hash:"set" is on the right fields
  4. Check pointer equality: &x and &y are different even if x == y
  5. Print individual field hashes: Hash each field separately to identify differences
// Debug individual fields
type User struct {
    Name string
    Age  int
}

u := User{Name: "Alice", Age: 30}
nameHash, _ := hashy.Hash(u.Name)
ageHash, _ := hashy.Hash(u.Age)
fmt.Printf("Name: %d, Age: %d\n", nameHash, ageHash)
📝 Versioning Considerations

If you plan to store or compare hashes across application versions:

  • Document hash inputs: Clearly state which fields are included
  • Version your hash logic: Add a version prefix if hash algorithm changes
  • Test compatibility: Verify old data still produces expected hashes
  • Avoid breaking changes: Don't change field order or tag behavior
// Versioned hash
type VersionedData struct {
    Version int    `hash:"ignore"` // Don't hash version itself
    Data    string
}

func hashWithVersion(data VersionedData) (string, error) {
    hash, err := hashy.Hash16Padded(data)
    if err != nil {
        return "", err
    }
    return fmt.Sprintf("v%d:%s", data.Version, hash), nil
}

Error Handling

The library returns two main error types:

  1. ErrNotStringer: Field has hash:"string" tag but doesn't implement fmt.Stringer
  2. General errors: Invalid options, unsupported types
hash, err := hashy.Hash(data)
if err != nil {
    var notStringer *hashy.ErrNotStringer
    if errors.As(err, &notStringer) {
        log.Printf("Field %s needs Stringer implementation", notStringer.Field)
    } else {
        log.Printf("Hash error: %v", err)
    }
}

Limitations

  • Not cryptographically secure: Use standard library crypto/* for security
  • No guaranteed uniqueness: Hash collisions are theoretically possible
  • Struct field order matters: Reordering fields changes the hash
  • Unexported fields ignored: Only public fields are hashed
  • No cross-language compatibility: Hashes are Go-specific

Contributing

Contributions are welcome! Please see the main replify repository for contribution guidelines.

License

This library is part of the replify project.

Documentation

Overview

Package hashy provides deterministic, structural hashing of arbitrary Go values, including structs, slices, maps, and primitive types.

The package is built around a configurable hasher that traverses a value using reflection, feeds each field and element into a 64-bit FNV-1a hash function, and returns a reproducible uint64 digest. The same logical value always produces the same hash within a single binary; the hash is not stable across different Go versions or architectures.

Basic Usage

h, err := hashy.Hash(myStruct)
fmt.Printf("%016x\n", h)

// Multiple values are hashed as a tuple:
h, err = hashy.Hash(userID, role, timestamp)

Output Formats

Hash returns a raw uint64. Convenience wrappers encode the result in common formats:

Hash256(v)    → SHA-256 of the uint64, as a hex string
Hash16Padded(v)    → zero-padded 16-character hex string
Hash16(v) → hexadecimal string
Hash10(v) → decimal string
Hash32(v) → base-32 string
Hash64(v)→ base-64 string

Configuration

Hash behaviour can be tuned by passing an *Options value (built via NewOptions().WithTagName(...).WithZeroNil(true).Build()) as the final variadic argument:

opts := hashy.NewOptions().WithSlicesAsSets(true).Build()
h, err := hashy.Hash(mySlice, opts)

Notable options include ZeroNil (treat nil pointers as zero values), IgnoreZeroValue (omit zero-value fields from the hash), SlicesAsSets (order-independent slice hashing), and UseStringer (use fmt.Stringer when available). The TagName option controls which struct tag is inspected for per-field directives such as "ignore" or "set".

Structs may implement the Hashable, FieldSelector, or MapSelector interfaces to customise how they are hashed.

hashy is safe for concurrent use when options are nil or were built with WithHasherFunc. Sharing options built with WithHasher across goroutines causes a data race because a single hash.Hash64 instance is not goroutine-safe.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultOptions

func DefaultOptions() *hashOptions

DefaultOptions returns default options for hashing.

Returns:

  • A pointer to a newly created `Options` instance with the default values.

func Hash

func Hash(data ...any) (uint64, error)

Hash generates a 64-bit hash value for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Examples:

hash, err := Hash(myStruct)                    // Single value
hash, err := Hash(val1, val2, val3)            // Multiple values
hash, err := Hash(myStruct, opts)              // With options
hash, err := Hash(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

Returns:

  • uint64: The computed hash value (never zero for valid inputs)
  • error: Non-nil if hashing fails

func Hash10

func Hash10(data ...any) (string, error)

Hash10 generates a decimal hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := Hash10(myStruct)                    // Single value
hash, err := Hash10(val1, val2, val3)            // Multiple values
hash, err := Hash10(myStruct, opts)              // With options
hash, err := Hash10(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

func Hash16

func Hash16(data ...any) (string, error)

Hash16 generates a hexadecimal hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := Hash16(myStruct)                    // Single value
hash, err := Hash16(val1, val2, val3)            // Multiple values
hash, err := Hash16(myStruct, opts)              // With options
hash, err := Hash16(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

func Hash16Padded

func Hash16Padded(data ...any) (string, error)

Hash16Padded generates a hexadecimal hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := Hash16Padded(myStruct)                    // Single value
hash, err := Hash16Padded(val1, val2, val3)            // Multiple values
hash, err := Hash16Padded(myStruct, opts)              // With options
hash, err := Hash16Padded(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

func Hash32

func Hash32(data ...any) (string, error)

Hash32 generates a base32 encoded hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := Hash32(myStruct)                    // Single value
hash, err := Hash32(val1, val2, val3)            // Multiple values
hash, err := Hash32(myStruct, opts)              // With options
hash, err := Hash32(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.§

func Hash64

func Hash64(data ...any) (string, error)

Hash64 generates a base64 encoded hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := Hash64(myStruct)                    // Single value
hash, err := Hash64(val1, val2, val3)            // Multiple values
hash, err := Hash64(myStruct, opts)              // With options
hash, err := Hash64(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

func Hash256

func Hash256(data ...any) (string, error)

Hash256 generates a 256-bit hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := Hash256(myStruct)                    // Single value
hash, err := Hash256(val1, val2, val3)            // Multiple values
hash, err := Hash256(myStruct, opts)              // With options
hash, err := Hash256(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

func HashHex16

func HashHex16(data ...any) (string, error)

HashHex16 generates a hexadecimal hash string for the given data. It accepts variadic arguments - if only one argument is provided, it hashes that value. If multiple arguments are provided, it hashes them as a tuple. The last argument can optionally be *Options.

Returns:

  • string: The computed hash string (never empty for valid inputs)
  • error: Non-nil if hashing fails

Example:

hash, err := HashHex16(myStruct)                    // Single value
hash, err := HashHex16(val1, val2, val3)            // Multiple values
hash, err := HashHex16(myStruct, opts)              // With options
hash, err := HashHex16(val1, val2, val3, opts)      // Multiple values with options

The hash is deterministic: identical values always produce identical hashes.

func HashValue

func HashValue(value any, options *hashOptions) (uint64, error)

HashValue generates a 64-bit hash value for a single value with options. This is the primary hashing function.

Parameters:

  • value: Any Go value (struct, slice, map, primitive, etc.)
  • options: Optional configuration (nil uses defaults)

Returns:

  • uint64: The computed hash value (never zero for valid inputs)
  • error: Non-nil if hashing fails

Concurrency: HashValue is safe for concurrent use provided that either (a) options is nil, or (b) the options were built with WithHasherFunc so that a fresh hash.Hash64 is created for every call. Reusing options that carry a single hash.Hash64 instance (via WithHasher) from multiple goroutines simultaneously causes a data race.

Example:

value := 1
hash, err := HashValue(value, nil)
fmt.Println(hash, err) // 1 nil

func NewHash

func NewHash(algo HashAlgorithm) hash.Hash

NewHash creates a new hash.Hash instance for the given algorithm.

Parameters:

  • algo: The hash algorithm to use.

Returns:

  • hash.Hash: The hash.Hash instance.

func NewHash64

func NewHash64(algo HashAlgorithm) hash.Hash64

NewHash64 creates a new hash.Hash64 instance for the given algorithm. Algorithms that do not natively implement hash.Hash64 (e.g. MD5, SHA-*) fall back to FNV-1a rather than panicking.

Parameters:

  • algo: The hash algorithm to use.

Returns:

  • hash.Hash64: The hash.Hash64 instance.

Types

type ErrNotStringer

type ErrNotStringer struct {
	Field string
}

ErrNotStringer is returned when there's an error with hash:"string"

Parameters:

  • Field: The name of the field that caused the error.

Returns:

  • A pointer to the `ErrNotStringer` struct.

func (*ErrNotStringer) Error

func (e *ErrNotStringer) Error() string

Error returns the error message for the `ErrNotStringer` type.

Returns:

  • A string representing the error message.

Example:

err := &ErrNotStringer{Field: "name"}
fmt.Println(err.Error()) // "pkg.hash: field \"name\" has hash:\"string\" tag but does not implement fmt.Stringer"

type FieldSelector

type FieldSelector interface {
	SelectField() SelectField
}

FieldSelector is an interface that can optionally be implemented by a struct. It will be called for each field in the struct to check whether it should be included in the hash.

type HashAlgorithm

type HashAlgorithm string

HashAlgorithm represents a hash algorithm.

const (
	// H_CRC32 is a CRC32 hash algorithm.
	H_CRC32 HashAlgorithm = "crc32"

	// H_CRC64 is a CRC64 hash algorithm.
	H_CRC64 HashAlgorithm = "crc64"

	// H_MD5 is a MD5 hash algorithm.
	H_MD5 HashAlgorithm = "md5"

	// H_SHA1 is a SHA1 hash algorithm.
	H_SHA1 HashAlgorithm = "sha1"

	// H_SHA224 is a SHA224 hash algorithm.
	H_SHA224 HashAlgorithm = "sha224"

	// H_SHA256 is a SHA256 hash algorithm.
	H_SHA256 HashAlgorithm = "sha256"

	// H_SHA384 is a SHA384 hash algorithm.
	H_SHA384 HashAlgorithm = "sha384"

	// H_SHA512 is a SHA512 hash algorithm.
	H_SHA512 HashAlgorithm = "sha512"

	// H_SHA512_224 is a SHA512_224 hash algorithm.
	H_SHA512_224 HashAlgorithm = "sha512_224"

	// H_SHA512_256 is a SHA512_256 hash algorithm.
	H_SHA512_256 HashAlgorithm = "sha512_256"
)

type Hashable

type Hashable interface {
	Hash() (uint64, error)
}

Hashable is an interface that can optionally be implemented by a struct. It will be called to get the hash of the struct. It returns a string representing the hash of the struct. If the function returns an error, the hash will not be included in the hash.

type MapSelector

type MapSelector interface {
	SelectMapEntry() SelectMapEntry
}

MapSelector is an interface that can optionally be implemented by a struct. It will be called for each map field in the struct to check whether it should be included in the hash.

type OptionsBuilder

type OptionsBuilder struct {
	// contains filtered or unexported fields
}

OptionsBuilder provides a fluent interface for building Options. It allows for chaining of method calls to configure the Options struct.

func NewOptions

func NewOptions() *OptionsBuilder

NewOptions creates a new options builder with defaults.

Returns:

  • A pointer to a newly created `OptionsBuilder` instance with the default values.

func (*OptionsBuilder) Build

func (b *OptionsBuilder) Build() *hashOptions

Build builds the options.

Returns:

  • A pointer to the `Options` struct.

Example:

builder := NewOptions().Build()
opts := builder.Build()

func (*OptionsBuilder) WithHasher deprecated

func (b *OptionsBuilder) WithHasher(h hash.Hash64) *OptionsBuilder

WithHasher sets the hash function to use.

Parameters:

  • h: The hash function to use.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Deprecated: prefer WithHasherFunc for concurrent-safe use. A single hash.Hash64 instance is stateful; passing the same *hashOptions to Hash/HashValue from multiple goroutines concurrently causes a data race.

Example:

builder := NewOptions().WithHasher(fnv.New64a())
opts := builder.Build()

func (*OptionsBuilder) WithHasherFunc

func (b *OptionsBuilder) WithHasherFunc(fn func() hash.Hash64) *OptionsBuilder

WithHasherFunc sets a factory function that creates a fresh hash.Hash64 for every hashing operation. Using a factory function is safe for concurrent use by multiple goroutines.

Parameters:

  • fn: A function that returns a new hash.Hash64 instance each time it is called.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Example:

builder := NewOptions().WithHasherFunc(fnv.New64a)
opts := builder.Build()

func (*OptionsBuilder) WithIgnoreZeroValue

func (b *OptionsBuilder) WithIgnoreZeroValue(ignore bool) *OptionsBuilder

WithIgnoreZeroValue sets whether zero value fields should be ignored for hash calculation.

Parameters:

  • ignore: A boolean indicating whether zero value fields should be ignored for hash calculation.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Example:

builder := NewOptions().WithIgnoreZeroValue(true)
opts := builder.Build()

func (*OptionsBuilder) WithSlicesAsSets

func (b *OptionsBuilder) WithSlicesAsSets(asSets bool) *OptionsBuilder

WithSlicesAsSets sets whether slices should be treated as sets.

Parameters:

  • asSets: A boolean indicating whether slices should be treated as sets.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Example:

builder := NewOptions().WithSlicesAsSets(true)
opts := builder.Build()

func (*OptionsBuilder) WithTagName

func (b *OptionsBuilder) WithTagName(name string) *OptionsBuilder

WithTagName sets the struct tag to look at when hashing the structure.

Parameters:

  • name: The name of the struct tag to look at.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Example:

builder := NewOptions().WithTagName("json")
opts := builder.Build()

func (*OptionsBuilder) WithUseStringer

func (b *OptionsBuilder) WithUseStringer(useStringer bool) *OptionsBuilder

WithUseStringer sets whether fmt.Stringer should be used always.

Parameters:

  • useStringer: A boolean indicating whether fmt.Stringer should be used always.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Example:

builder := NewOptions().WithUseStringer(true)
opts := builder.Build()

func (*OptionsBuilder) WithZeroNil

func (b *OptionsBuilder) WithZeroNil(zeroNil bool) *OptionsBuilder

WithZeroNil sets whether nil pointer should be treated equal to a zero value of pointed type.

Parameters:

  • zeroNil: A boolean indicating whether nil pointer should be treated equal to a zero value of pointed type.

Returns:

  • A pointer to the `OptionsBuilder` struct.

Example:

builder := NewOptions().WithZeroNil(true)
opts := builder.Build()

type SelectField

type SelectField func(field string, value any) (bool, error)

SelectField is a function that can be used to check if a field should be included in the hash. It returns a boolean indicating whether the field should be included in the hash. If the function returns an error, the field will not be included in the hash.

type SelectMapEntry

type SelectMapEntry func(field string, k, v any) (bool, error)

SelectMapEntry is a function that can be used to check if a map field should be included in the hash. It returns a boolean indicating whether the map field should be included in the hash. If the function returns an error, the map field will not be included in the hash.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL