Rlibphonenumber
A zero-allocation, high-performance Rust port of Google's libphonenumber library for parsing, formatting, and validating international phone numbers.
Used metadata version: v9.0.25
Version: 1.1.0
Base libphonenumber: 9.0.8
Min supported Rust version: 1.88.0
🛡️ Correctness
Through over 11.2 million iterations of malformed, randomized, and edge-case inputs, this library has proven zero mismatches in parsing, validation rules (is_valid, is_possible), and formatting outputs (E.164, National, International, RFC3966) compared to the upstream C++ implementation. It provides exact drop-in behavior with Rust's memory safety and high execution speed.
Performance
Performance is measured using criterion. We compare rlibphonenumber with the popular rust-phonenumber (the phonenumber crate) and phonelib crates.
All benchmarks measure the average time required to process a single phone number.
Initialization
rlibphonenumber requires initializing PhoneNumberUtil, which loads the necessary metadata. This is typically done once at application startup:
PhoneNumberUtil::new(): ~6.41 ms
Parsing
Time required to parse a string representation into a phone number object:
| Library | parse() |
Notes |
|---|---|---|
rlibphonenumber |
~568 ns | Fastest & most reliable |
rust-phonenumber |
~832 ns | Fails on certain valid numbers.* |
phonelib |
Failed | Fails on certain valid numbers. |
* During testing, we found that rust-phonenumber (rlp) returns an error on valid phone numbers, such as the Brazilian number "+55 11 98765-4321".
Formatting
Time required to format a parsed phone number object into various standards:
| Format | rlibphonenumber |
rust-phonenumber |
phonelib |
|---|---|---|---|
| E164 | ~33.7 ns 🚀 | ~721 ns | ~710 ns |
| International | ~423 ns | ~1.01 µs | ~772 ns |
| National | ~562 ns | ~1.38 µs | ~752 ns |
| RFC3966 | ~587 ns | ~1.18 µs | ~906 ns |
Under the Hood: How is it so fast?
- Zero-Allocation Formatting: Intermediate heap allocations are eliminated. By utilizing
Cow<str>, stack-allocated buffers (via a custom zero-paddingitoaimplementation), and a specialized Builder pattern, formatting numbers rarely touches the system allocator. - Build-Time Anchored Regexes (
RegexTriplets): Instead of allocating strings at runtime to wrap patterns in^(?:...)$, a custom Java build script pre-compiles and wraps metadata directly into the Protobuf output. At runtime, Rust uses[..]string slicing (zero-cost) to extract exact bounds, bypassing the regex engine's O(N) linear search and forcingO(1)fast-fail anchor matching. - Fast Hashing: Replaces the default
SipHashwithFxHash(rustc_hash) for ultra-low-latency metadata lookups by region code and integer keys. - Lazy Initialization: Regular expressions are compiled lazily and cached on-demand directly inside metadata wrappers using
std::sync::OnceLock, removing the locking overhead of a centralized regex cache.
Installation & Feature Flags
Add rlibphonenumber to your Cargo.toml. You can choose between the standard regex engine (fastest parsing) or the lite engine (smallest binary size).
1. Standard (Recommended for Backend/Desktop)
Uses the full regex crate. Provides maximum parsing performance.
[]
= "1.1.0"
2. Lite (Recommended for WASM/Embedded)
Uses regex-lite to significantly reduce binary size. Parsing is slower than the standard backend but still efficient enough for UI/Validation tasks. Formatting speed remains virtually identical.
[]
= { = "1.1.0", = false, = ["lite", "global_static"] }
Available Features
| Feature | Description | Default |
|---|---|---|
regex |
Uses the regex crate (SIMD optimizations, large Unicode tables). Best for speed. |
✅ |
lite |
Uses regex-lite. Optimizes for binary size. Best for WASM or embedded targets. |
❌ |
global_static |
Enables the lazy-loaded global PHONE_NUMBER_UTIL instance. |
✅ |
serde |
Enables Serialize/Deserialize for PhoneNumber. |
❌ |
Getting Started
The library exposes a global static PHONE_NUMBER_UTIL, but for most common operations, you can use methods directly on the PhoneNumber struct.
Complete Example
use ;
Serde Integration
When the serde feature is enabled, PhoneNumber serializes to a string (E.164 format) and can be deserialized from a string.
use PhoneNumber;
use json;
Differential Fuzzing
We invite anyone to verify our correctness parity. The repository includes a Dockerized environment that links Google's C++ libphonenumber side-by-side with our Rust implementation via cxx.
To run the differential fuzzer locally:
- Clone the repository and open the provided DevContainer/Docker environment.
- Run the
full-cyclefuzz target to check fully random user inputs and ensure no panics occur: - Run the
diff-testtarget to compare outputs with the original library (requires the C++ library version to match the metadata version used):
If the fuzzer ever finds a single input where the Rust output deviates from the C++ output, it will immediately crash and save the artifact.
Manual Instantiation
By default, this crate enables the global_static feature, which initializes a thread-safe, lazy-loaded static instance PHONE_NUMBER_UTIL. This allows you to use convenience methods directly on PhoneNumber.
If you need granular control over memory usage, wish to avoid global state, or are working in a strict environment, you can disable this feature.
[]
= { = "1.1.0", = false, = ["regex"] }
When global_static is disabled, helper methods on PhoneNumber (like .format_as(), .is_valid()) will not be available. You must instantiate the utility manually.
⚠️ Performance Note: PhoneNumberUtil::new() compiles regexes upon initialization. This is an expensive operation. Create it once and reuse it (e.g., wrap it in an Arc or pass it by reference).
use ;