ai generated
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.idea
|
||||||
169
Cargo.lock
generated
Normal file
169
Cargo.lock
generated
Normal 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
12
Cargo.toml
Normal 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
98
examples/basic_usage.rs
Normal 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
12
hash2_derive/Cargo.toml
Normal 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
126
hash2_derive/src/lib.rs
Normal 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
218
src/lib.rs
Normal 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
164
tests/derive_test.rs
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user