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