ai generated

This commit is contained in:
Intege-rs
2026-02-03 03:33:48 -05:00
commit f8e3e4ae8e
8 changed files with 801 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
.idea

169
Cargo.lock generated Normal file
View File

@@ -0,0 +1,169 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bumpalo"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "hash2"
version = "0.1.0"
dependencies = [
"hash2_derive",
"ordered-float",
"uuid",
]
[[package]]
name = "hash2_derive"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "js-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"num-traits",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "uuid"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
dependencies = [
"unicode-ident",
]

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "hash2"
version = "0.1.0"
edition = "2024"
[features]
[dependencies]
ordered-float = "5.1.0"
hash2_derive = { path = "hash2_derive" }
uuid = { version = "*", optional = true }

98
examples/basic_usage.rs Normal file
View File

@@ -0,0 +1,98 @@
use hash2::Hash2;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
// Basic struct with derive
#[derive(Hash2)]
pub struct User {
pub id: u64,
pub name: String,
pub age: u8,
}
// Struct with generics
#[derive(Hash2)]
pub struct Container<T> {
pub value: T,
pub count: usize,
}
// Struct with lifetimes
#[derive(Hash2)]
pub struct Borrowed<'a> {
pub data: &'a str,
pub id: u64,
}
// Struct with both generics and lifetimes
#[derive(Hash2)]
pub struct Complex<'a, T> {
pub owned: T,
pub borrowed: &'a str,
}
// Example with PhantomData (like the original Test struct)
#[derive(Hash2)]
pub struct Test<'a> {
pub test: f32,
pub test2: u64,
pub _ignored: std::marker::PhantomData<&'a ()>,
}
// Enum example
#[derive(Hash2)]
pub enum Status {
Active,
Inactive,
Pending { since: u64 },
}
fn compute_hash<T: Hash2>(value: &T) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
fn main() {
// Basic struct
let user = User {
id: 42,
name: "Alice".to_string(),
age: 30,
};
println!("User hash: {}", compute_hash(&user));
// Generic struct
let container = Container {
value: 100u32,
count: 5,
};
println!("Container hash: {}", compute_hash(&container));
// Borrowed struct
let text = String::from("Hello");
let borrowed = Borrowed {
data: &text,
id: 1,
};
println!("Borrowed hash: {}", compute_hash(&borrowed));
// Complex struct
let complex = Complex {
owned: vec![1, 2, 3],
borrowed: &text,
};
println!("Complex hash: {}", compute_hash(&complex));
// Test struct (original example)
let test = Test {
test: 3.14,
test2: 999,
_ignored: std::marker::PhantomData,
};
println!("Test hash: {}", compute_hash(&test));
// Enum
let status = Status::Pending { since: 12345 };
println!("Status hash: {}", compute_hash(&status));
}

12
hash2_derive/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "hash2_derive"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"

126
hash2_derive/src/lib.rs Normal file
View File

@@ -0,0 +1,126 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericParam, Generics};
#[proc_macro_derive(Hash2)]
pub fn derive_hash2(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
// Extract field hashing logic
let hash_fields = match &input.data {
Data::Struct(data) => {
match &data.fields {
Fields::Named(fields) => {
let field_names = fields.named.iter().map(|f| &f.ident);
quote! {
#(
::hash2::Hash2::hash(&self.#field_names, state);
)*
}
}
Fields::Unnamed(fields) => {
let field_indices = (0..fields.unnamed.len()).map(syn::Index::from);
quote! {
#(
::hash2::Hash2::hash(&self.#field_indices, state);
)*
}
}
Fields::Unit => {
quote! {}
}
}
}
Data::Enum(data) => {
let variants = data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
match &variant.fields {
Fields::Named(fields) => {
let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
let field_names2 = field_names.clone();
quote! {
#name::#variant_name { #(#field_names),* } => {
#(
::hash2::Hash2::hash(#field_names2, state);
)*
}
}
}
Fields::Unnamed(fields) => {
let field_names: Vec<_> = (0..fields.unnamed.len())
.map(|i| syn::Ident::new(&format!("f{}", i), proc_macro2::Span::call_site()))
.collect();
let field_names2 = field_names.clone();
quote! {
#name::#variant_name(#(#field_names),*) => {
#(
::hash2::Hash2::hash(#field_names2, state);
)*
}
}
}
Fields::Unit => {
quote! {
#name::#variant_name => {}
}
}
}
});
quote! {
match self {
#(#variants)*
}
}
}
Data::Union(_) => {
panic!("Hash2 cannot be derived for unions");
}
};
// Build where clause with Hash2 bounds for generic types
let where_clause = build_where_clause(generics);
// Split generics for impl
let (impl_generics, ty_generics, _) = generics.split_for_impl();
let expanded = quote! {
impl #impl_generics ::hash2::Hash2 for #name #ty_generics #where_clause {
fn hash<H: ::hash2::Hasher>(&self, state: &mut H) {
#hash_fields
}
}
};
TokenStream::from(expanded)
}
fn build_where_clause(generics: &Generics) -> proc_macro2::TokenStream {
let mut predicates = Vec::new();
// Add Hash2 bound for each type parameter (but not lifetimes)
for param in &generics.params {
if let GenericParam::Type(type_param) = param {
let ident = &type_param.ident;
predicates.push(quote! { #ident: ::hash2::Hash2 });
}
}
// Include existing where predicates
if let Some(where_clause) = &generics.where_clause {
for predicate in &where_clause.predicates {
predicates.push(quote! { #predicate });
}
}
if predicates.is_empty() {
quote! {}
} else {
quote! {
where #(#predicates),*
}
}
}

218
src/lib.rs Normal file
View File

@@ -0,0 +1,218 @@
pub use core::hash::Hasher;
pub use hash2_derive::Hash2;
use std::hash::Hash;
use ordered_float::OrderedFloat;
pub trait Hash2 {
fn hash<H: Hasher>(&self, state: &mut H);
}
#[cfg(feature = "uuid")]
impl Hash2 for uuid::Uuid {
fn hash<H: Hasher>(&self, state: &mut H) {
let (a,b) = self.as_u64_pair();
state.write_u64(a);
state.write_u64(b);
}
}
// Unsigned integer implementations
impl Hash2 for u8 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u8(*self);
}
}
impl Hash2 for u16 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u16(*self);
}
}
impl Hash2 for u32 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(*self);
}
}
impl Hash2 for u64 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(*self);
}
}
impl Hash2 for u128 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u128(*self);
}
}
impl Hash2 for usize {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(*self);
}
}
// Signed integer implementations
impl Hash2 for i8 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_i8(*self);
}
}
impl Hash2 for i16 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_i16(*self);
}
}
impl Hash2 for i32 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_i32(*self);
}
}
impl Hash2 for i64 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_i64(*self);
}
}
impl Hash2 for i128 {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_i128(*self);
}
}
impl Hash2 for isize {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_isize(*self);
}
}
// floats
impl Hash2 for f32 {
fn hash<H: Hasher>(&self, state: &mut H) {
OrderedFloat(*self).hash(state)
}
}
impl Hash2 for f64 {
fn hash<H: Hasher>(&self, state: &mut H) {
OrderedFloat(*self).hash(state)
}
}
// String types
impl Hash2 for str {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(self.as_bytes());
state.write_usize(self.len());
}
}
impl Hash2 for String {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash2::hash(self.as_str(), state);
}
}
impl<T: Hash2 + ?Sized> Hash2 for &T {
fn hash<H: Hasher>(&self, state: &mut H) {
(*self).hash(state);
}
}
impl<T: Hash2 + ?Sized> Hash2 for &mut T {
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).hash(state);
}
}
impl<T: Hash2> Hash2 for [T] {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_usize(self.len());
for item in self {
item.hash(state);
}
}
}
impl<T: Hash2, const N: usize> Hash2 for [T; N] {
fn hash<H: Hasher>(&self, state: &mut H) {
for item in self {
item.hash(state);
}
}
}
impl<T: Hash2> Hash2 for Vec<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_slice().hash(state);
}
}
impl<T: Hash2> Hash2 for Option<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
None => state.write_u8(0),
Some(value) => {
state.write_u8(1);
value.hash(state);
}
}
}
}
impl Hash2 for bool {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u8(*self as u8);
}
}
impl Hash2 for char {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(*self as u32);
}
}
// Tuple implementations
impl<T: ?Sized> Hash2 for std::marker::PhantomData<T> {
fn hash<H: Hasher>(&self, _state: &mut H) {}
}
impl Hash2 for () {
fn hash<H: Hasher>(&self, _state: &mut H) {}
}
impl<T: Hash2> Hash2 for (T,) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T: Hash2, U: Hash2> Hash2 for (T, U) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.1.hash(state);
}
}
impl<T: Hash2, U: Hash2, V: Hash2> Hash2 for (T, U, V) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.1.hash(state);
self.2.hash(state);
}
}
impl<T: Hash2, U: Hash2, V: Hash2, W: Hash2> Hash2 for (T, U, V, W) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.1.hash(state);
self.2.hash(state);
self.3.hash(state);
}
}

164
tests/derive_test.rs Normal file
View File

@@ -0,0 +1,164 @@
use hash2::Hash2;
#[derive(Hash2)]
struct SimpleStruct {
a: u32,
b: u64,
}
#[derive(Hash2)]
struct GenericStruct<T> {
value: T,
count: usize,
}
#[derive(Hash2)]
struct LifetimeStruct<'a> {
name: &'a str,
id: u64,
}
#[derive(Hash2)]
struct BothGenerics<'a, T> {
data: T,
reference: &'a str,
number: i32,
}
#[derive(Hash2)]
struct MultipleTypes<T, U> {
first: T,
second: U,
}
#[derive(Hash2)]
struct MultipleLifetimes<'a, 'b> {
first: &'a str,
second: &'b str,
}
#[derive(Hash2)]
struct Complex<'a, 'b, T, U>
where
T: Clone,
U: Clone,
{
data1: T,
data2: U,
ref1: &'a str,
ref2: &'b str,
}
#[derive(Hash2)]
enum SimpleEnum {
A,
B(u32),
C { x: i32, y: i32 },
}
#[derive(Hash2)]
enum GenericEnum<T> {
None,
Some(T),
Pair(T, T),
}
fn hash_value<T: Hash2>(value: &T) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
std::hash::Hasher::finish(&hasher)
}
#[test]
fn test_simple_struct() {
let s = SimpleStruct { a: 42, b: 1000 };
let hash1 = hash_value(&s);
let hash2 = hash_value(&s);
assert_eq!(hash1, hash2, "Same struct should produce same hash");
}
#[test]
fn test_generic_struct() {
let s1 = GenericStruct { value: 42u32, count: 10 };
let s2 = GenericStruct { value: 42u32, count: 10 };
assert_eq!(hash_value(&s1), hash_value(&s2));
let s3 = GenericStruct { value: "hello", count: 10 };
let _ = hash_value(&s3);
}
#[test]
fn test_lifetime_struct() {
let name = String::from("test");
let s = LifetimeStruct { name: &name, id: 123 };
let hash1 = hash_value(&s);
let hash2 = hash_value(&s);
assert_eq!(hash1, hash2);
}
#[test]
fn test_both_generics() {
let reference = String::from("ref");
let s = BothGenerics {
data: 42u32,
reference: &reference,
number: -10,
};
let _ = hash_value(&s);
}
#[test]
fn test_multiple_types() {
let s = MultipleTypes {
first: 42u32,
second: "hello",
};
let _ = hash_value(&s);
}
#[test]
fn test_multiple_lifetimes() {
let first = String::from("first");
let second = String::from("second");
let s = MultipleLifetimes {
first: &first,
second: &second,
};
let _ = hash_value(&s);
}
#[test]
fn test_complex() {
let ref1 = String::from("ref1");
let ref2 = String::from("ref2");
let s = Complex {
data1: 42u32,
data2: "data",
ref1: &ref1,
ref2: &ref2,
};
let _ = hash_value(&s);
}
#[test]
fn test_simple_enum() {
let e1 = SimpleEnum::A;
let e2 = SimpleEnum::B(42);
let e3 = SimpleEnum::C { x: 1, y: 2 };
let _ = hash_value(&e1);
let _ = hash_value(&e2);
let _ = hash_value(&e3);
}
#[test]
fn test_generic_enum() {
let e1: GenericEnum<u32> = GenericEnum::None;
let e2 = GenericEnum::Some(42u32);
let e3 = GenericEnum::Pair(1u32, 2u32);
let _ = hash_value(&e1);
let _ = hash_value(&e2);
let _ = hash_value(&e3);
}