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.24
Version: 1.0.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
rlibphonenumber is designed for low latency and minimal memory overhead. Latest benchmarks show a significant performance advantage over existing Rust alternatives.
| Operation | rlibphonenumber (v1.0.0) | rust-phonenumber | Difference |
|---|---|---|---|
| Format (E164) | ~258 ns | ~8.94 µs | ~34.6x faster |
| Format (International) | ~3.82 µs | ~11.55 µs | ~3.0x faster |
| Format (National) | ~4.95 µs | ~15.63 µs | ~3.1x faster |
| Format (RFC3966) | ~5.02 µs | ~12.79 µs | ~2.5x faster |
| Parse | ~4.70 µs | ~8.36 µs | ~1.8x faster |
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
Add rlibphonenumber to your Cargo.toml:
[]
= "1.0.0"
Enabling Serde
To enable Serialize and Deserialize support for PhoneNumber:
[]
= { = "1.0.0", = ["serde"] }
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 & Feature Flags
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.0.0", = false }
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 ;