Library / SDK
rust-phf/rust-phf avatar
rust-phf/rust-phf

rust-phf: perfect hash lookup tables resolved while your crate compiles

Compile time static maps for Rust

2,190 stars141 forksRustMIT

At a glance

What is it?
The phf family turns a literal list of keys into a compiled lookup table with no runtime hashing, no allocation and no hash table probing. Here is what it actually replaces, what it costs, and where the macro fights you.
Who is it for?
phf earns its place when a key set is known when the crate is compiled: keyword tables, HTTP status names, enum discriminants, fixed symbol lists. It is a poor fit for keys that arrive from a config file at runtime, because the table has to be fixed before the binary exists, and switching to a runtime built map then means pulling in a second data structure.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What a perfect hash removes from a lookup

A `HashMap` resolves a key by hashing it, indexing a bucket array, walking a chain, and comparing keys. Every lookup pays for that, and the collection also carries a capacity, load factor and allocation that exist to absorb collisions. A perfect hash function removes the collisions instead of tolerating them. Each key in a closed set maps to a distinct slot, so a lookup becomes a hash, one index and one comparison, with no probe loop.

Rust-PHF exists to get that property into a `static`. The crate description is compile time static maps for Rust, and the README says the library generates efficient lookup tables at compile time using perfect hash functions. The generation happens on your machine during `cargo build`, inside a procedural macro or a code generator, so the finished binary contains only the array and the hash function. Nothing is measured at startup and nothing is guessed at runtime.

The repository is a Cargo workspace rather than a single crate. `phf/` holds the runtime data structures and map types, `phf_macros/` the `phf_map!` procedural macro, `phf_codegen/` the builder API, `phf_generator/` the hashing itself, and `phf_shared/` the pieces both the macro and the code generator need. The top level also carries `build_docs.sh`, `deny.toml` and `RELEASE_PROCESS.md`, which tells you the crate takes dependency licensing seriously.

Declaring a keyword table with phf_map

The macro path is the shortest route to a working table. You write the keys and values as literals inside the invocation, and the macro does the hashing, verifies the result is collision free, and emits the finished map as a static. Adding the feature and using the macro looks like this:

toml
[dependencies]
phf = { version = "0.14.0", features = ["macros"] }

The README's example builds a keyword parser from an enum, which is the shape you will reach for most often because the key set is whatever your language already reserves:

rust
use phf::phf_map;

static KEYWORDS: phf::Map<&'static str, Keyword> = phf_map! {
    "loop" => Keyword::Loop,
    "continue" => Keyword::Continue,
    "break" => Keyword::Break,
    "fn" => Keyword::Fn,
    "extern" => Keyword::Extern,
};

Lookups go through `get`, and the return types differ by value type. For a copyable value the example calls `.cloned()`, and for a `&'static str` value it calls `.copied()`, so `parse_keyword` returns `Option<Keyword>` and `parse_operator` returns `Option<&'static str>`. That is the ergonomic cost of the design: you are reading a map that cannot fail to hold the key, yet the compiler still hands you an `Option` because the key you pass in is a runtime string that may not be in the table.

The macro also accepts OR patterns, so several spellings can collapse onto one value without duplicating the entry: `"+" | "add" | "plus" => "addition"`. The README builds exactly that for operators.

Generating the same table from build.rs

When the key list is too long to sit comfortably in a macro, or is derived rather than written by hand, the second route is `phf_codegen`. It exposes a builder you call from a build script, which writes the generated static to `OUT_DIR` and includes it with `include!`. The dependencies go under the build table rather than the normal one:

toml
[build-dependencies]
phf = { version = "0.14.0", default-features = false }
phf_codegen = "0.14.0"

The README's build script creates the file, then chains `.entry()` calls onto `phf_codegen::Map::new()` and finishes with `.build()`, writing the result into a `phf::Map<&'static str, Keyword>` declaration. The generated code is then pulled in from `lib.rs`:

rust
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

This route has a real advantage over the macro: the key set can come from somewhere else, such as a file or a generated list, and the Rust source stays short. It also has a real cost. The generated output is invisible unless you go looking for it in the target directory, so a surprising key set becomes a debugging session rather than a reading exercise. And where the macro gives you OR patterns for free, the README states plainly that `phf_codegen` does not support OR patterns directly, so the operator example becomes three separate `.entry()` calls with the same value.

The README also points at the online generated documentation at docs.rs for phf, which is where the builder signature and the emitted representation are actually specified.

Enabling ptrhash on the runtime and the generator together

The default algorithm is CHD, taken from the cpmph project's paper. Alongside it there is an experimental `ptrhash` feature offering a different minimal perfect hash layout. The word experimental is doing real work in that sentence, and the important part of the README is not the algorithm but the wiring instruction.

You must enable `ptrhash` on both sides. If you use the macro:

toml
[dependencies]
phf = { version = "0.14.0", features = ["macros", "ptrhash"] }

If you use the code generator, the README says to enable it on both `phf_codegen` and the runtime `phf` dependency, so the generated constants match the runtime struct layout. That is not a stylistic preference. A perfect hash function only produces meaningful indices if the code that reads them agrees with the code that computed them, and a mismatch between the two feature flags produces a table that compiles, links, and returns the wrong value. This is the single detail to watch when you adopt the experimental layout.

There is also a `no_std` route that has nothing to do with hashing. Setting `default-features = false` for the dependency builds `phf` against `libcore` instead of `libstd`, which the README describes as enabling use in environments where `libstd` will not work. That is the reason the build script's dependency list above also disables default features.

Key shapes the macro only partly accepts

The README carries an explicit note that the macro syntax has some limitations and may not work as you want, pointing at issue 196, and then lists the ones that will actually bite you. These are worth reading before you design a key type.

Integer literals in the first key's type shape must carry explicit suffixes, such as `0u32`, `[0u8, 1]`, or `(0u32, 1u32)`. Later keys infer their unsuffixed integer literals from the same position in that first key, so the first entry is effectively a type declaration for the rest of the table. Tuple keys are supported up to twelve elements, which is a real limit if your keys are coordinate pairs and you were hoping for a wider tuple. All keys must use the same supported key expression type as the first key, so a heterogeneous table is not expressible.

None of these are bugs to be worked around so much as design constraints to design around. The practical response is to key the table on something uniform, usually a string or a single integer, and put the variation in the value. An enum value already does that job, which is exactly what the keyword example does.

Two version numbers that do not agree on the floor

Here the repository's own records disagree, and it is better to name both than to pick one. The README states that the minimum supported rust version is Rust 1.85. The release notes for v0.14.0, published on 2026-06-21, list a change titled bump MSRV to 1.68, attributed to a pull request in the same release. So the README on the default branch describes a higher floor than the floor the 1.0 releases were raised to.

Both facts cannot describe the same crate version at the same time. The release note is version specific and dated, while the README describes the branch as it stands, so the likeliest reading is that the floor moved again after v0.14.0 shipped and the README picked up the newer number. What is less settled is which release carries it. There is no separate changelog entry in the repository tree confirming a later bump, so if your build environment is pinned to an older toolchain, the README number is the one to try first and the 1.68 number is the one that would let you skip the upgrade if the crate turns out to accept it.

Related to that, the workspace `Cargo.toml` declares `resolver = "1"`. A workspace that requires Rust 1.85 would normally get resolver 2 from its edition, so the explicit setting is a deliberate pin rather than a leftover. Set against the rest of the repository, which shows regular dependency maintenance through the release notes, this reads as a project that is still being developed. The last push was on 2026-09-26 and the repository is not archived. The license is MIT.

Editorial conclusion

phf earns its place when a key set is known when the crate is compiled: keyword tables, HTTP status names, enum discriminants, fixed symbol lists. It is a poor fit for keys that arrive from a config file at runtime, because the table has to be fixed before the binary exists, and switching to a runtime built map then means pulling in a second data structure. The concrete starting point is the `macros` feature for a literal table, since it needs no build script, and the thing to check first in your own codebase is whether your keys really are static, because everything else follows from that. The README leaves the macro's rough edges at issue 196, and that issue is worth reading before you commit to a key shape.

Frequently asked questions

What does the phf crate do in Rust?

It generates lookup tables at compile time using perfect hash functions, so a table of known keys needs no hashing work, allocation or collision probing at runtime. The keys and values are written as literals and the emitted table lives in a `static` inside your binary.

When should I use phf instead of a standard HashMap?

Use it when the key set is fixed when the crate compiles: keywords, status names, symbol tables. A `HashMap` is the better fit when keys arrive from a config file, a database or user input at runtime, because a perfect hash can only be computed once the full key set is known.

What is the difference between phf_macros and phf_codegen?

`phf_macros` provides the `phf_map!` procedural macro for tables you write inline, and it supports OR patterns so several keys can share a value. `phf_codegen` is a builder you call from `build.rs` for larger or derived key sets, and the README notes that it does not support OR patterns directly.

Does phf work without the standard library?

Yes. Setting `default-features = false` on the dependency builds `phf` against `libcore` instead of `libstd`, which the README describes as enabling use in environments where `libstd` will not work.

Why do I have to enable ptrhash on two crates?

The generated constants have to match the runtime struct layout. If you enable `ptrhash` on the generator but not on the runtime `phf` crate, the table compiles and links but looks up the wrong values, so the README says to enable it on both.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. rust-phf/rust-phf on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rust-phf-rust-phf.svg)](https://hysenlabs.com/projects/rust-phf-rust-phf)