A high-performance Rust port of Google's libphonenumber library for parsing, formatting, and validating international phone numbers.
Built on base libphonenumber 9.0.8 Used metadata version: v9.0.24
This library is a fresh adaptation of Google's libphonenumber for Rust. Its primary goal is to provide a powerful and efficient tool for handling phone numbers, with a structure that is intuitively close to the original C++ version, but adapted for Rust ergonomics.
Key capabilities include:
- Parsing and formatting phone numbers.
- Validating phone numbers for all regions of the world.
- Determining the number type (e.g., Mobile, Fixed-line, Toll-free).
- Serde support (optional).
- Thread-safe and high performance.
rlibphonenumber is designed for speed. Benchmarks show significant improvements over existing alternatives, particularly in formatting operations.
| Format | rlibphonenumber (this crate) | rust-phonenumber | Performance Gain |
|---|---|---|---|
| E164 | ~668 ns | ~12.82 µs | ~19x faster |
| International | ~11.76 µs | ~17.20 µs | ~1.5x faster |
| National | ~15.19 µs | ~22.66 µs | ~1.5x faster |
| Parse | ~11.60 µs | ~13.45 µs | ~16% faster |
Add rlibphonenumber to your Cargo.toml:
[dependencies]
rlibphonenumber = "0.3.1"To enable Serialize and Deserialize support for PhoneNumber:
[dependencies]
rlibphonenumber = { version = "0.3.0", features = ["serde"] }The library exposes a global static PHONE_NUMBER_UTIL, but for most common operations, you can now use methods directly on the PhoneNumber struct.
use rlibphonenumber::{
PHONE_NUMBER_UTIL,
PhoneNumber,
PhoneNumberFormat,
ParseError,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let number_string = "+1-587-530-2271";
// 1. Parse the number
// You can use the standard FromStr trait:
let number: PhoneNumber = number_string.parse()?;
// Or explicitly via the utility:
// let number = PHONE_NUMBER_UTIL.parse(number_string)?;
println!("✅ Successfully parsed number.");
println!(" - Country Code: {}", number.country_code());
println!(" - National Number: {}", number.national_number());
// 2. Validate the number
// `is_valid()` performs a full validation (length, prefix, region rules)
let is_valid = number.is_valid();
println!(
"\nIs the number valid? {}",
if is_valid { "Yes" } else { "No" }
);
if !is_valid {
return Ok(());
}
// 3. Format the number
// Display trait uses E164 by default
println!("\nDefault Display: {}", number);
let e164_format = number.format_as(PhoneNumberFormat::E164);
let international_format = number.format_as(PhoneNumberFormat::International);
let national_format = number.format_as(PhoneNumberFormat::National);
let rfc3966_format = number.format_as(PhoneNumberFormat::RFC3966);
println!("Formatted Outputs:");
println!(" - E.164: {}", e164_format); // +15875302271
println!(" - International: {}", international_format); // +1 587-530-2271
println!(" - National: {}", national_format); // (587) 530-2271
println!(" - RFC3966: {}", rfc3966_format); // tel:+1-587-530-2271
// 4. Get additional information
let number_type = number.get_type(); // e.g., Mobile, FixedLine
let region_code = number.get_region_code(); // e.g., "CA"
println!("\nInfo:");
println!(" - Type: {:?}", number_type);
println!(" - Region: {:?}", region_code.unwrap_or("Unknown"));
Ok(())
}When the serde feature is enabled, PhoneNumber serializes to a string (E.164 format) and can be deserialized from a string.
use rlibphonenumber::PhoneNumber;
use serde_json::json;
fn main() {
let raw = "+15875302271";
let number: PhoneNumber = raw.parse().unwrap();
// Serializes to "+15875302271"
let json_output = json!({ "phone": number });
println!("{}", json_output);
}The library is under active development. The core PhoneNumberUtil is fully implemented and passes the original library's test suite.
Roadmap:
AsYouTypeFormatter: For formatting phone numbers as a user types.PhoneNumberOfflineGeocoder: To provide geographical location information.PhoneNumberToCarrierMapper: To identify the carrier associated with a number.
Contributions are highly welcome! Whether you are fixing a bug, improving documentation, or helping to port a new module, your help is appreciated.
To maintain consistency with the original library, this project uses pre-compiled metadata. If you need to regenerate the metadata (e.g., after updating PhoneNumberMetadata.xml), use the provided script:
./tools/scripts/generate_metadata.sh --tag v9.0.23Beyond standard unit tests that cover expected behavior, rlibphonenumber undergoes rigorous fuzz testing to ensure its resilience against unexpected, malformed, and potentially malicious input. Given that phone number parsing often deals with untrusted data from users, stability is a primary design goal.
-
Install prerequisites:
rustup default nightly cargo install cargo-fuzz
-
Run a fuzz target:
full-cycle: A comprehensive test of the parse -> validate -> format workflow.
To start a fuzzing session, run:
# Example for the main target cargo fuzz run full-cycle
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 (e.g., number.is_valid()).
However, if you need granular control over memory usage, wish to avoid global state, or are working in an environment where lazy statics are undesirable, you can disable this feature.
In your Cargo.toml, disable the default features:
[dependencies]
rlibphonenumber = { version = "0.3.0", default-features = false }When global_static is disabled, the PHONE_NUMBER_UTIL constant and the helper methods on PhoneNumber (like .format_as(), .is_valid()) will not be available. You must instantiate the utility manually and pass it around.
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 rlibphonenumber::{PhoneNumberUtil, PhoneNumber};
fn main() {
// 1. Initialize the utility once (expensive operation)
let phone_util = PhoneNumberUtil::new();
let number_str = "+15550109988";
// 2. Parse using the instance
// Note: 'parse' is a method on phone_util, not a trait on str here
match phone_util.parse(number_str) {
Ok(number) => {
// 3. Use the instance for validation and formatting
// number.is_valid() is NOT available without 'global_static'
let is_valid = phone_util.is_valid_number(&number);
println!("Valid: {}", is_valid);
}
Err(e) => eprintln!("Parse error: {:?}", e),
}
}This project is licensed under the Apache License, Version 2.0. Please see the LICENSE file for details.