1 module cachetools.hash;
2 
3 import std.traits;
4 
5 ///
6 /// For classes (and structs with toHash method) we use v.toHash() to compute hash.
7 /// ===============================================================================
8 /// toHash method CAN BE @nogc or not. HashMap 'nogc' properties is inherited from this method.
9 /// toHash method MUST BE @safe or @trusted, as all HashMap code alredy safe.
10 ///
11 /// See also: https://dlang.org/spec/hash-map.html#using_classes_as_key 
12 /// and https://dlang.org/spec/hash-map.html#using_struct_as_key
13 ///
14 bool UseToHashMethod(T)() {
15     return (is(T == class) || (is(T == struct) && __traits(compiles, {
16                 T v = T.init;
17                 hash_t h = v.toHash();
18             })));
19 }
20 
21 hash_t hash_function(T)(in T v) @safe /* @nogc inherited from toHash method */
22 if (UseToHashMethod!T) {
23     return v.toHash();
24 }
25 
26 hash_t hash_function(T)(in T v) @nogc @trusted if (!UseToHashMethod!T) {
27     static if (isNumeric!T) {
28         enum m = 0x5bd1e995;
29         hash_t h = v;
30         h ^= h >> 13;
31         h *= m;
32         h ^= h >> 15;
33         return h;
34     } else static if (is(T == string)) {
35         // FNV-1a hash
36         ulong h = 0xcbf29ce484222325;
37         foreach (const ubyte c; cast(ubyte[]) v) {
38             h ^= c;
39             h *= 0x100000001b3;
40         }
41         return cast(hash_t) h;
42     } else {
43         const(ubyte)[] bytes = (cast(const(ubyte)*)&v)[0 .. T.sizeof];
44         ulong h = 0xcbf29ce484222325;
45         foreach (const ubyte c; bytes) {
46             h ^= c;
47             h *= 0x100000001b3;
48         }
49         return cast(hash_t) h;
50     }
51 }
52 
53 @safe unittest {
54     assert(hash_function("abc") == cast(hash_t) 0xe71fa2190541574b);
55 
56     struct A0 {
57     }
58 
59     assert(!UseToHashMethod!A0);
60 
61     struct A1 {
62         hash_t toHash() const @safe {
63             return 0;
64         }
65     }
66 
67     assert(UseToHashMethod!A1);
68 
69     // class with toHash override - will use toHash
70     class C0 {
71         override hash_t toHash() const @safe {
72             return 0;
73         }
74     }
75 
76     assert(UseToHashMethod!C0);
77     C0 c0 = new C0();
78     assert(c0.toHash() == 0);
79 
80     // class without toHash override - use Object.toHash method
81     class C1 {
82     }
83 
84     assert(UseToHashMethod!C1);
85 }