hashcrew 0.1.0

Fast, portable non-cryptographic hashes for Rust
Documentation
// Copyright 2026 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Fast, portable, non-cryptographic hash functions.
//!
//! APIs are grouped by family under [`cityhash`], [`xxhash`], [`murmur`], and
//! [`fnv`]. Use the free functions for complete byte slices and state types for
//! incremental input. CityHash is intentionally one-shot. Outputs that fit in
//! 64 bits also implement [`core::hash::Hasher`].
//!
//! Raw digests are stable across platforms for identical byte streams. The
//! [`core::hash`] adapters use Rust's native typed encodings and are not a
//! portable serialization format. These hashes are deterministic and are
//! **not cryptographically secure**.
//!
//! # Choosing an algorithm
//!
//! Prefer XXH3 for new checksums, cache keys, and trusted-input hash tables.
//! The CityHash, MurmurHash3, FNV-1a, XXH32, and XXH64 APIs are primarily for
//! interoperability with an existing format or data set. Choose a 128-bit
//! variant when the application needs a lower collision probability than a
//! 64-bit digest provides.
//!
//! # API model
//!
//! Choose the interface from the form of input rather than from a separate
//! implementation:
//!
//! * Call a module-level function such as [`xxhash::xxh3_64`] when the complete byte slice is
//!   available.
//! * Construct a state such as [`xxhash::Xxh3_64`], call its `update` method for each slice, and
//!   call `digest` when input is complete. `digest` does not consume the state, and `reset`
//!   preserves its configuration.
//! * With the default `std` feature, use the same state as [`std::io::Write`] when bytes come from
//!   an I/O producer.
//! * For a Rust hash collection, pass the matching builder as its [`core::hash::BuildHasher`].
//!   These adapters consume Rust's typed [`core::hash::Hash`] encoding rather than a portable byte
//!   serialization.
//!
//! ## Capability map
//!
//! | Variant             | Complete input                               | Incremental state                          | Digest | [`Hasher`](core::hash::Hasher) / builder                                                          |
//! |---------------------|----------------------------------------------|--------------------------------------------|--------|---------------------------------------------------------------------------------------------------|
//! | CityHash32          | [`cityhash32`](cityhash::cityhash32)         | —                                          | `u32`  | —                                                                                                 |
//! | CityHash64          | [`cityhash64`](cityhash::cityhash64)*        | —                                          | `u64`  | —                                                                                                 |
//! | CityHash128         | [`cityhash128`](cityhash::cityhash128)*      | —                                          | `u128` | —                                                                                                 |
//! | XXH32               | [`xxh32`](xxhash::xxh32)                     | [`Xxh32`](xxhash::Xxh32)                   | `u32`  | [`Xxh32`](xxhash::Xxh32) / [`Xxh32Builder`](xxhash::Xxh32Builder)                                 |
//! | XXH64               | [`xxh64`](xxhash::xxh64)                     | [`Xxh64`](xxhash::Xxh64)                   | `u64`  | [`Xxh64`](xxhash::Xxh64) / [`Xxh64Builder`](xxhash::Xxh64Builder)                                 |
//! | XXH3-64             | [`xxh3_64`](xxhash::xxh3_64)*                | [`Xxh3_64`](xxhash::Xxh3_64)               | `u64`  | [`Xxh3_64`](xxhash::Xxh3_64) / [`Xxh3_64Builder`](xxhash::Xxh3_64Builder)                         |
//! | XXH3-128            | [`xxh3_128`](xxhash::xxh3_128)*              | [`Xxh3_128`](xxhash::Xxh3_128)             | `u128` | —                                                                                                 |
//! | MurmurHash3 x86_32  | [`murmur3_x86_32`](murmur::murmur3_x86_32)   | [`Murmur3X86_32`](murmur::Murmur3X86_32)   | `u32`  | [`Murmur3X86_32`](murmur::Murmur3X86_32) / [`Murmur3X86_32Builder`](murmur::Murmur3X86_32Builder) |
//! | MurmurHash3 x86_128 | [`murmur3_x86_128`](murmur::murmur3_x86_128) | [`Murmur3X86_128`](murmur::Murmur3X86_128) | `u128` | —                                                                                                 |
//! | MurmurHash3 x64_128 | [`murmur3_x64_128`](murmur::murmur3_x64_128) | [`Murmur3X64_128`](murmur::Murmur3X64_128) | `u128` | —                                                                                                 |
//! | FNV-1a 32           | [`fnv1a_32`](fnv::fnv1a_32)*                 | [`Fnv1a32`](fnv::Fnv1a32)                  | `u32`  | [`Fnv1a32`](fnv::Fnv1a32) / [`Fnv1a32Builder`](fnv::Fnv1a32Builder)                               |
//! | FNV-1a 64           | [`fnv1a_64`](fnv::fnv1a_64)*                 | [`Fnv1a64`](fnv::Fnv1a64)                  | `u64`  | [`Fnv1a64`](fnv::Fnv1a64) / [`Fnv1a64Builder`](fnv::Fnv1a64Builder)                               |
//!
//! A trailing `*` indicates additional explicitly named configuration forms.
//! [`xxhash::Xxh3_64SecretBuilder`] provides the custom-secret XXH3-64 hash-table
//! adapter. The 128-bit states do not implement [`core::hash::Hasher`] because
//! its [`finish`](core::hash::Hasher::finish) method can only return `u64`.
//! CityHash has no streaming state because bounded-memory incremental hashing
//! cannot reproduce its one-shot algorithm. MurmurHash3's `x86` and `x64`
//! labels distinguish incompatible algorithms, not target requirements.
//!
//! # Feature flags
//!
//! The crate is dependency-free and allocation-free in every feature
//! configuration. The default `std` feature integrates streaming states with
//! [`std::io`] and enables runtime CPU-feature detection for XXH3. Disable
//! default features for `no_std` targets; hardware kernels are then selected
//! only from features guaranteed by the target, with scalar code as the
//! fallback. Feature selection does not change digest values.
//!
//! ```toml
//! [dependencies]
//! hashcrew = { version = "0.1", default-features = false }
//! ```
//!
//! # Streaming input
//!
//! Call `update` when the application already receives byte slices. With the
//! default `std` feature, every incremental state can also be the destination
//! of [`std::io::copy`] or another producer that writes to [`std::io::Write`].
//! Bytes written to the state become hash input: `write` accepts the complete
//! buffer, and `flush` has no work to perform. Obtain the digest separately
//! after the producer finishes.
//!
//! ```
//! # #[cfg(feature = "std")]
//! # {
//! use std::io;
//!
//! use hashcrew::xxhash::Xxh3_64;
//! use hashcrew::xxhash::xxh3_64;
//!
//! let input = b"hashcrew";
//! let mut state = Xxh3_64::new();
//! io::copy(&mut input.as_slice(), &mut state).unwrap();
//!
//! assert_eq!(state.digest(), xxh3_64(input));
//! # }
//! ```
//!
//! # Complete and incremental hashing
//!
//! Hash a complete byte slice with a free function, or feed the same bytes to
//! a reusable state:
//!
//! ```
//! use hashcrew::xxhash::Xxh64;
//! use hashcrew::xxhash::xxh64;
//!
//! let expected = xxh64(b"hashcrew", 42);
//! let mut state = Xxh64::with_seed(42);
//! state.update(b"hash");
//! state.update(b"crew");
//!
//! assert_eq!(state.digest(), expected);
//! ```

#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]
#![forbid(unsafe_op_in_unsafe_fn)]

pub mod cityhash;
pub mod fnv;
pub mod murmur;
pub mod xxhash;

#[cfg(test)]
extern crate std;

#[inline(always)]
fn read_u32(input: &[u8], offset: usize) -> u32 {
    let bytes: [u8; 4] = input[offset..offset + 4]
        .try_into()
        .expect("validated hash input range");
    u32::from_le_bytes(bytes)
}

#[inline(always)]
fn read_u64(input: &[u8], offset: usize) -> u64 {
    let bytes: [u8; 8] = input[offset..offset + 8]
        .try_into()
        .expect("validated hash input range");
    u64::from_le_bytes(bytes)
}

#[inline(always)]
fn fmix32(mut value: u32) -> u32 {
    value ^= value >> 16;
    value = value.wrapping_mul(0x85eb_ca6b);
    value ^= value >> 13;
    value = value.wrapping_mul(0xc2b2_ae35);
    value ^ (value >> 16)
}

#[inline(always)]
fn mul128_fold64(lhs: u64, rhs: u64) -> u64 {
    let product = u128::from(lhs) * u128::from(rhs);
    product as u64 ^ (product >> 64) as u64
}