1use std::{
2 fmt,
3 sync::{
4 atomic::{AtomicUsize, Ordering},
5 Mutex,
6 },
7};
8
9#[derive(Debug)]
14pub struct BloomFilter {
15 bits: Mutex<Vec<u64>>,
16 bit_count: usize,
17 hash_functions: u32,
18 insertions: AtomicUsize,
19}
20
21impl BloomFilter {
22 pub fn new(bit_count: usize, hash_functions: u32) -> Result<Self, BloomError> {
24 if bit_count == 0 {
25 return Err(BloomError::ZeroBits);
26 }
27 if hash_functions == 0 {
28 return Err(BloomError::ZeroHashFunctions);
29 }
30
31 Ok(Self {
32 bits: Mutex::new(vec![0; bit_count.div_ceil(64)]),
33 bit_count,
34 hash_functions,
35 insertions: AtomicUsize::new(0),
36 })
37 }
38
39 pub fn with_rate(expected_items: usize, false_positive_rate: f64) -> Result<Self, BloomError> {
41 if expected_items == 0 {
42 return Err(BloomError::ZeroExpectedItems);
43 }
44 if !(0.0..1.0).contains(&false_positive_rate) {
45 return Err(BloomError::InvalidFalsePositiveRate(false_positive_rate));
46 }
47
48 let item_count = expected_items as f64;
49 let logarithm = std::f64::consts::LN_2;
50 let bit_count =
51 (-(item_count * false_positive_rate.ln()) / logarithm.powi(2)).ceil() as usize;
52 let hash_functions = ((bit_count as f64 / item_count) * logarithm)
53 .round()
54 .max(1.0) as u32;
55 Self::new(bit_count, hash_functions)
56 }
57
58 pub fn insert(&self, value: impl AsRef<[u8]>) -> bool {
60 let (first, second) = hashes(value.as_ref());
61 let mut bits = self.bits.lock().expect("Bloom filter mutex poisoned");
62 let mut changed = false;
63
64 for index in self.indices(first, second) {
65 let word = index / 64;
66 let mask = 1_u64 << (index % 64);
67 changed |= bits[word] & mask == 0;
68 bits[word] |= mask;
69 }
70
71 if changed {
72 self.insertions.fetch_add(1, Ordering::Relaxed);
73 }
74 changed
75 }
76
77 pub fn contains(&self, value: impl AsRef<[u8]>) -> bool {
79 let (first, second) = hashes(value.as_ref());
80 let bits = self.bits.lock().expect("Bloom filter mutex poisoned");
81 self.indices(first, second).all(|index| {
82 let word = index / 64;
83 let mask = 1_u64 << (index % 64);
84 bits[word] & mask != 0
85 })
86 }
87
88 pub fn bit_count(&self) -> usize {
89 self.bit_count
90 }
91
92 pub fn hash_functions(&self) -> u32 {
93 self.hash_functions
94 }
95
96 pub fn insertions(&self) -> usize {
98 self.insertions.load(Ordering::Relaxed)
99 }
100
101 pub fn clear(&self) {
102 self.bits
103 .lock()
104 .expect("Bloom filter mutex poisoned")
105 .fill(0);
106 self.insertions.store(0, Ordering::Relaxed);
107 }
108
109 fn indices(&self, first: u64, second: u64) -> impl Iterator<Item = usize> + '_ {
110 (0..self.hash_functions).map(move |iteration| {
111 first
112 .wrapping_add(u64::from(iteration).wrapping_mul(second))
113 .wrapping_rem(self.bit_count as u64) as usize
114 })
115 }
116}
117
118fn hashes(bytes: &[u8]) -> (u64, u64) {
119 const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
120 const PRIME: u64 = 0x100000001b3;
121
122 let first = bytes.iter().fold(OFFSET_BASIS, |hash, byte| {
123 (hash ^ u64::from(*byte)).wrapping_mul(PRIME)
124 });
125 let second = bytes
126 .iter()
127 .rev()
128 .fold(OFFSET_BASIS ^ 0x9e3779b97f4a7c15, |hash, byte| {
129 (hash ^ u64::from(*byte)).wrapping_mul(PRIME)
130 })
131 | 1;
132 (first, second)
133}
134
135#[derive(Debug, Clone, PartialEq)]
136pub enum BloomError {
137 ZeroBits,
138 ZeroHashFunctions,
139 ZeroExpectedItems,
140 InvalidFalsePositiveRate(f64),
141}
142
143impl fmt::Display for BloomError {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::ZeroBits => {
147 formatter.write_str("Bloom filter bit count must be greater than zero")
148 }
149 Self::ZeroHashFunctions => {
150 formatter.write_str("Bloom filter hash count must be greater than zero")
151 }
152 Self::ZeroExpectedItems => {
153 formatter.write_str("Bloom filter expected item count must be greater than zero")
154 }
155 Self::InvalidFalsePositiveRate(rate) => write!(
156 formatter,
157 "Bloom filter false-positive rate must be between zero and one: {rate}"
158 ),
159 }
160 }
161}
162
163impl std::error::Error for BloomError {}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn inserted_items_are_never_missing() {
171 let filter = BloomFilter::with_rate(1_000, 0.001).unwrap();
172 for item in 0..1_000 {
173 filter.insert(item.to_string());
174 }
175
176 for item in 0..1_000 {
177 assert!(filter.contains(item.to_string()));
178 }
179 assert_eq!(filter.insertions(), 1_000);
180 }
181
182 #[test]
183 fn reports_absent_items_and_can_be_cleared() {
184 let filter = BloomFilter::new(1_024, 4).unwrap();
185 filter.insert("known");
186
187 assert!(filter.contains("known"));
188 assert!(!filter.contains("definitely-absent"));
189 filter.clear();
190 assert!(!filter.contains("known"));
191 assert_eq!(filter.insertions(), 0);
192 }
193
194 #[test]
195 fn validates_filter_dimensions() {
196 assert_eq!(BloomFilter::new(0, 1).unwrap_err(), BloomError::ZeroBits);
197 assert_eq!(
198 BloomFilter::new(1, 0).unwrap_err(),
199 BloomError::ZeroHashFunctions
200 );
201 assert!(matches!(
202 BloomFilter::with_rate(100, 1.0),
203 Err(BloomError::InvalidFalsePositiveRate(1.0))
204 ));
205 }
206}