faker2 is a port of the Python Faker project to
Rust, with a thin Python binder — import faker2 runs the compiled Rust engine
(via PyO3); no fake-data logic lives in Python. It keeps Faker's 151 locales,
generates the same kinds of data ~3–18× faster, and adds name-intelligence
features the original doesn't have.
import faker2
f = faker2.Faker(42) # seedable, deterministic
f.gen("fr_FR", "name") # 'Édouard Guichard'
f.gen("ja_JP", "address") # Japanese address
f.gen("en_US", "credit_card_number") # Luhn-valid
f.homophones("Dominique", "FR") # (name, probability, phonetic_similarity)Upstream Faker docs: README.upstream.rst.
Python ↔ Rust parity matrix: PARITY.md.
Python — from PyPI (Rust-backed wheels, abi3, for Linux x86_64/aarch64, macOS Intel/Apple-Silicon, Windows):
pip install faker2Rust — the crate (git for now):
cargo add faker2 --git https://github.com/jqueguiner/faker2 --features real-names,localesPrebuilt CLI binaries and wheels are attached to every GitHub Release.
import faker2 loads the compiled Rust core (faker2._native, PyO3). No
fake-data logic runs in Python.
import faker2
f = faker2.Faker(42) # seedable; Faker() for random
f.gen("fr_FR", "name") # any of 151 locales, ~213 formatters
f.email(); f.address(); f.company() # en_US convenience methods
# name intelligence (real 1.43M-name, 139-country dataset)
f.first_name_like_real("Jacques", "FR") # gender-preserving, freq-weighted
faker2.infer_gender_real("Mohammed", "EG") # "m"
faker2.detect_country("Yuki") # [("JP", 0.58), ...]
faker2.homophones("Dominique", "FR", "balanced", 5, True, None)
# -> [("Dominique", 0.94, 1.0), ("Dominic", 0.02, 0.80), ...] (name, prob, similarity)
faker2.locales() # 151Module functions: infer_gender_real, detect_country, homophones,
available_countries, locales, locale_formatters. Class: Faker.
use faker2::Faker;
let f = Faker::seeded(42);
f.gen("ja_JP", "name"); // -> Option<String>, 151 locales
f.first_name_like_real("Jacques", Some("FR")); // needs the `real-names` feature
Faker::homophones("Dominique", "FR", "balanced", 5, true, None);
// -> Vec<(String /*name*/, f64 /*prob*/, f64 /*similarity*/)>
Faker::detect_country("Yuki", 3);Features: real-names (parquet name dataset + homophones), locales
(151-locale gen). See rust/README.md.
faker2/ Python package (upstream faker fork)
naming/ ← added: name intelligence
gender.py gender infer + preserving replace (bundled locales)
realnames.py same, backed by data/ ground truth (needs pyarrow)
grammar.py pluralize / singularize / agreement
data/ ← added: name ground truth
first_names.parquet 1.43M names, 139 countries, gender, relative frequency
last_names.parquet small sample (surnames NOT comprehensive)
README.md schema + provenance
rust/ ← added: Rust port
src/ engine, providers, gender, grammar, realnames
README.md crate docs
tests/ Python tests (incl. test_gender_grammar.py)
from faker2.naming import realnames, grammar
realnames.infer_gender("Jacques", "FR") # "m"
realnames.first_name_like("Jacques", "FR") # frequency-weighted male FR name
realnames.first_name("JP", "f") # weighted female Japanese name
realnames.detect_country("Yuki") # [("JP", 0.58), ("CN", 0.05), ...]
realnames.detect_country("Bjorn") # [("SE", 0.29), ("NO", 0.22), ...]
realnames.homophones("Dominique", "FR") # [("Dominique", 0.94, 1.0), ("Dominic", 0.02, 0.80), ...] (name, prob, g2p similarity)
grammar.pluralize("baby") # "babies"
grammar.agree(3, "dog") # "3 dogs"realnames needs pyarrow (in dev-requirements.txt). The bundled-locale
variant faker2.naming.gender has no extra dependency.
detect_country ranks where a name is most characteristic (its within-country
frequency share), not where the most people with that name live — raw
population counts are intentionally not in the dataset.
homophones returns same-sounding names in a country as
(name, probability, similarity) triples — probability = frequency share
within the group (sums to 1); similarity = g2p2 per-language phonetic
similarity (0..1) of that name to the query. Pick the matching method:
"metaphone"(default) — double-metaphone group; fast but coarse (may pull in near-homophones, e.g. Sophie↔Xavier)."ipa"— per-language phonetics: every name in the dataset is phonemized in its own language with g2p2 (theg2p_ipacolumn) and scored with g2p2's articulatory feature-weighted alignment — so Mohammed→Mohammad, Giovanni→Gianni use real Arabic/Italian phonetics, not an anglocentric transcription."levenshtein"— spelling withinmax_distanceedits; orthographic variants."balanced"— g2p2 phonetics + spelling consensus with per-country weights swept offline (data/balanced_params.json, 119 countries). Drops metaphone-only collisions (Sophie↛Xavier).
realnames.homophones("Dominique", "FR", method="ipa") # (name, prob, similarity)
realnames.homophones("Marc", "FR", method="levenshtein", max_distance=1)
realnames.homophones("Sophie", "FR", method="balanced") # per-country tuned; no XavierThe balanced weights are tuned by scripts/sweep_balanced.py, which grid-searches
(w_ipa, min, cap) per country against a spelling+metaphone consensus objective,
using g2p2 similarity on the per-language g2p_ipa. Regenerate with
PYTHONPATH=. python3 scripts/sweep_balanced.py (needs g2p2, pyarrow).
cd rust
cargo test # zero-dependency core
cargo run -- --seed 42 name
cargo run -- like Jacques fr # gender-preserving replacement
cargo test --features real-names # opt-in parquet ground truth
See rust/README.md.
Original Faker = the pure-Python package (from faker2 import Faker, the fork of
joke2k/Faker). This port = the Rust engine
(import faker2, or the crate directly). Same machine, en_US.
Full per-formatter table (63 formatters): BENCHMARKS.md —
62/63 are faster in Rust, geo-mean ~20×.
| Formatter | Python | Rust | Speedup |
|---|---|---|---|
name |
0.017 M ops/s | 0.315 M ops/s | ~18× |
email |
0.017 M ops/s | 0.296 M ops/s | ~17× |
address |
0.012 M ops/s | 0.162 M ops/s | ~13× |
credit_card_number (Luhn) |
0.084 M ops/s | 0.292 M ops/s | ~3.5× |
iban (mod-97) |
0.068 M ops/s | 0.344 M ops/s | ~5× |
| Metric | Python | Rust | Note |
|---|---|---|---|
| CPU / wall time | 29 s | 2 s | ~15× faster |
| Peak RAM (RSS) | 26 MB | 120 MB | Rust eagerly loads all 151 locales (flat); Python lazy-loads one |
RAM is the one honest trade-off: the Rust engine holds the entire 151-locale dataset resident (constant ~120 MB) for instant locale switching, whereas Python loads a locale lazily. For multi-locale bulk work Rust's footprint stays flat while Python's grows per locale.
| Check | Python | Rust |
|---|---|---|
| Credit-card Luhn | 100% | 100% |
| IBAN ISO-7064 mod-97 | 100% | 100% |
| EAN-13 GS1 checksum | 100% | 100% |
Identical: both produce valid, checksum-correct data. The Rust checksums are
independently verified in rust/tests/algo.rs.
| Python (original) | Rust (this port) | |
|---|---|---|
| Locales | 151 | 151 |
| Formatters | ~230 | 213 (93%) |
Every locale is covered, and 100% of the single-string formatters are ported
(name, address, credit cards, IBAN, barcodes, colors, dates, network, python
scalars, geo, doi, passport, user-agent, lorem, gendered names, json/csv/xml, …).
The ~17 remaining return non-string data — raw bytes (binary, image,
json_bytes, tar, zip), Python collections (pylist, pydict, pyset,
profile, …) or need an argument (enum) — not applicable to a single-string
generator.
Name intelligence backed by a real 1.43M-name / 139-country dataset — not in upstream Faker:
infer_gender_real(name, country)— gender from real data.first_name_like(name, country)— gender-preserving, frequency-weighted replacement.detect_country(name)— most likely origin country of a name.homophones(name, country, method=…)— same-sounding names + probabilities (metaphone / IPA / levenshtein / balanced, per-country tuned).- grammar agreement (pluralize / singularize / count agreement).
# Python (original)
PYTHONPATH=. python3 - <<'PY'
from faker2 import Faker; import time
f=Faker("en_US"); n=200000; t=time.perf_counter()
for _ in range(n): f.name()
print(n/(time.perf_counter()-t)/1e6, "M ops/s")
PY
# Rust (this port)
cd rust && cargo run --release --features locales --example benchfull # if keptThe name dataset was extracted from a PostgreSQL dump and normalized so
frequency is a relative share — no raw population counts are exposed. See
data/README.md.