quickphf_codegen/lib.rs
1#![forbid(unsafe_code)]
2
3//! `quickphf_codegen` is a Rust crate that allows you to generate static hash
4//! maps and hash sets at compile-time using
5//! [PTHash perfect hash functions](https://arxiv.org/abs/2104.10402).
6//!
7//! For the runtime code necessary to use these data structures check out the
8//! [`quickphf` crate](quickphf) instead.
9//!
10//! The minimum supported Rust version is 1.56. This crate uses
11//! `#![forbid(unsafe_code)]`.
12//!
13//! **WARNING**: `quickphf` and `quickphf_codegen` currently use the standard
14//! library [`Hash`](https://doc.rust-lang.org/std/hash/trait.Hash.html)
15//! trait which is not portable between platforms with different endianness.
16//!
17//! ## Example
18//!
19//! Currently, the only way to generate data structures for use with `quickphf`
20//! is by running one of [`build_raw_map`], [`build_map`], or [`build_set`],
21//! displaying the result as a string, and then importing the resulting Rust
22//! code at the desired location.
23//!
24//! For example, you can write a
25//! [`build.rs` script](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
26//! such as:
27//!
28//! ```ignore
29//! use std::env;
30//! use std::fs::File;
31//! use std::io::{BufWriter, Write};
32//! use std::path::Path;
33//!
34//! fn main() {
35//! let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
36//! let mut file = BufWriter::new(fs::File::create(&path).unwrap());
37//!
38//! let keys = ["jpg", "png", "svg"];
39//! let values = ["image/jpeg", "image/png", "image/svg+xml"];
40//! let code = quickphf_codegen::build_map(&keys, &values);
41//!
42//! write!(&mut file, code).unwrap();
43//! }
44//! ```
45//!
46//! and then import the result in your `lib.rs` by:
47//!
48//! ```ignore
49//! static MIME_TYPES: quickphf::PhfMap<&'static str, &'static str> =
50//! include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
51//! ```
52//!
53//! ## Advanced Usage
54//!
55//! ### Using QuickPHF with custom types
56//!
57//! To be usable as a key in a `PhfMap` or `PhfSet`, or as value in a `RawPhfMap`
58//! or a `PhfMap`, a type must implement the trait [`ConstInstantiable`], which
59//! provides a way to generate code which instantiates any value of that type in
60//! a `const` context. This trait is already implemented for many built-in types,
61//! but users can also implement it for their own custom types, by one of two ways:
62//!
63//! 1. If the code required to instantiate a value of a type is identical to its
64//! `Debug` representation, for example, like the following enum:
65//!
66//! ```ignore
67//! #[derive(Debug, Hash, PartialEq, Eq)]
68//! enum PositionType {
69//! Contract { pub hours_per_week: u32 },
70//! Salaried,
71//! Managerial,
72//! }
73//! ```
74//!
75//! then it suffices to write
76//!
77//! ```ignore
78//! impl quickphf_codegen::DebugInstantiable for PositionType {}
79//! ```
80//!
81//! 2. Otherwise, the user has to provide a custom implementation. For example,
82//! the following struct has private fields and thus its values cannot be
83//! instantiated using the `{}` syntax, but provides a `new` constructor
84//! that is a `const fn`. Thus, given
85//!
86//! ```ignore
87//! #[derive(Debug, Hash, PartialEq, Eq)]
88//! struct EmploymentRules {
89//! overtime_eligible: bool,
90//! bonus_eligible: bool,
91//! }
92//!
93//! impl EmploymentRules {
94//! pub const fn new(overtime_eligible: bool, bonus_eligible: bool) -> EmploymentRules {
95//! EmploymentRules {
96//! overtime_eligible,
97//! bonus_eligible,
98//! }
99//! }
100//! }
101//! ```
102//!
103//! we can provide a custom `ConstInstantiable` implementation by
104//!
105//! ```ignore
106//! use core::fmt;
107//! use quickphf_codegen::*;
108//!
109//! impl ConstInstantiable for EmploymentRules {
110//! fn fmt_const_new(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111//! write!(
112//! f,
113//! "EmploymentRules::new({}, {})",
114//! self.overtime_eligible, self.bonus_eligible
115//! )
116//! }
117//! }
118//! ```
119
120use core::fmt;
121use core::hash::Hash;
122
123use phf::{generate_phf, Phf};
124
125mod const_instantiable;
126pub mod phf;
127
128pub use const_instantiable::ConstInstantiable;
129pub use const_instantiable::DebugInstantiable;
130
131/// Generate code for a static [`quickphf::RawPhfMap`].
132///
133/// # Examples
134///
135/// ```
136/// use quickphf_codegen::*;
137///
138/// let months = [
139/// "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec",
140/// ];
141/// let holidays = [2, 1, 0, 0, 0, 1, 1, 0, 1, 1, 2, 1];
142/// let holidays_per_month = build_raw_map(&months, &holidays);
143/// ```
144pub fn build_raw_map<'a, K: Eq + Hash, V: ConstInstantiable>(
145 keys: &'a [K],
146 values: &'a [V],
147) -> CodeWriter<'a, K, V> {
148 let phf = generate_phf::<K>(keys);
149 CodeWriter {
150 kind: Kind::RawMap,
151 phf,
152 keys: &[],
153 values,
154 }
155}
156
157/// Generate code for a static [`quickphf::PhfMap`].
158///
159/// # Examples
160///
161/// ```
162/// use quickphf_codegen::*;
163///
164/// let roots = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
165/// let fourth_powers = roots.map(|x| x * x * x * x);
166/// let powers_to_roots = build_map(&fourth_powers, &roots);
167/// ```
168pub fn build_map<'a, K: Eq + Hash + ConstInstantiable, V: ConstInstantiable>(
169 keys: &'a [K],
170 values: &'a [V],
171) -> CodeWriter<'a, K, V> {
172 let phf = generate_phf(keys);
173 CodeWriter {
174 kind: Kind::Map,
175 phf,
176 keys,
177 values,
178 }
179}
180
181/// Generate code for a static [`quickphf::PhfSet`].
182///
183/// # Examples
184///
185/// ```
186/// use quickphf_codegen::*;
187/// let digits_set = build_set(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
188/// ```
189pub fn build_set<K: Eq + Hash + ConstInstantiable>(keys: &[K]) -> CodeWriter<'_, K> {
190 let phf = generate_phf(keys);
191 CodeWriter {
192 kind: Kind::Set,
193 phf,
194 keys,
195 values: &[],
196 }
197}
198
199enum Kind {
200 RawMap,
201 Map,
202 Set,
203}
204
205/// Code generator for a PTHash perfect hash function hash table structure.
206pub struct CodeWriter<'a, K, V = ()> {
207 kind: Kind,
208 phf: Phf,
209 keys: &'a [K],
210 values: &'a [V],
211}
212
213impl<'a, K: ConstInstantiable, V: ConstInstantiable> fmt::Display for CodeWriter<'a, K, V> {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 self.write(f)
216 }
217}
218
219impl<'a, K: ConstInstantiable, V: ConstInstantiable> CodeWriter<'a, K, V> {
220 fn write(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 let type_name = match self.kind {
222 Kind::RawMap => "RawPhfMap",
223 Kind::Map => "PhfMap",
224 Kind::Set => "PhfSet",
225 };
226
227 writeln!(f, "::quickphf::{}::new(", type_name)?;
228 writeln!(f, " {},", self.phf.seed)?;
229
230 write!(f, " &")?;
231 self.write_slice(self.phf.pilots_table.iter(), f)?;
232 writeln!(f, ",")?;
233
234 //
235 let mut prev_entry = false;
236 write!(f, " &")?;
237 write!(f, "[")?;
238
239 for &idx in &self.phf.map {
240 if prev_entry {
241 write!(f, ", ")?;
242 } else {
243 prev_entry = true;
244 }
245
246 match self.kind {
247 Kind::Map => {
248 let key = &self.keys[idx as usize];
249 let value = &self.values[idx as usize];
250
251 write!(f, "(")?;
252 key.fmt_const_new(f)?;
253 write!(f, ", ")?;
254 value.fmt_const_new(f)?;
255 write!(f, ")")?;
256 }
257 Kind::RawMap => {
258 self.values[idx as usize].fmt_const_new(f)?;
259 }
260 Kind::Set => {
261 self.keys[idx as usize].fmt_const_new(f)?;
262 }
263 }
264 }
265 writeln!(f, "],")?;
266
267 write!(f, " &")?;
268 self.write_slice(self.phf.free.iter(), f)?;
269 writeln!(f)?;
270
271 write!(f, ")")
272 }
273
274 fn write_slice<T: ConstInstantiable + 'a>(
275 &'a self,
276 entries: impl Iterator<Item = &'a T>,
277 f: &mut fmt::Formatter<'_>,
278 ) -> fmt::Result {
279 write!(f, "[")?;
280
281 let mut prev_entry = false;
282 for entry in entries {
283 if prev_entry {
284 write!(f, ", ")?;
285 } else {
286 prev_entry = true;
287 }
288
289 entry.fmt_const_new(f)?;
290 }
291
292 write!(f, "]")
293 }
294}