How to Build a US Address Generator in Rust
Introduction
Imagine a software developer preparing to launch an online store. The website is nearly complete, the product pages look good, and the checkout process works smoothly. Then a problem appears during testing. The address form needs dozens of different sample records, but manually creating them takes too long, and using real people’s residential information raises privacy concerns.
This is where a US address generator in Rust becomes useful. By building a small program that produces fictional address records, developers can test forms, populate development databases, practise programming, and evaluate how applications handle address information.
Rust is a strong choice for this project because it offers performance, memory safety, and a well-organized ecosystem of libraries. Its package manager, Cargo, makes it straightforward to create a project and add dependencies. According to the official Cargo documentation, Cargo manages dependencies, compiles packages, and supports the Rust development workflow. <Cite refs={[“turn365371search2″,”turn365371search12”]}/>
In this guide, we will build a simple US address generator from scratch, improve its output, and explore how to export sample records. The goal is to create useful test data, not to invent addresses for use in real transactions. A fictional address should never be assumed to be a real or deliverable location.
Understanding How a US Address Generator Works
Before writing Rust code, it helps to understand what the program needs to generate. A typical US mailing address contains a street number and name, an optional apartment or suite, a city, a two-letter state abbreviation, and a ZIP code.
A fictional record for software testing might look like this:
Sample address for testing only
4827 Example Creek Drive
Sampletown, TX 00000
This example demonstrates the layout of an address. The street and city are fictional, and the ZIP code is a placeholder rather than a valid postal code.
A basic US address generator can select information from predefined collections and combine it into a structured record. A more advanced generator can support different states, optional apartment numbers, batch creation, and data export.
For beginners, the best approach is to start with a small command-line application. Once the generation logic works correctly, it can be extended into a web service, a desktop utility, or a component of a larger testing system.
Setting Up Rust and Cargo
The first step is to install Rust and make sure Cargo is available. The official Rust website provides installation guidance at https://www.rust-lang.org/tools/install, while the Cargo documentation explains how to manage packages and dependencies.
Create a new project by opening a terminal and running:
cargo new us_address_generator
cd us_address_generator
Cargo creates a project directory containing a Cargo.toml file and a src/main.rs source file. The manifest stores project information and lists external libraries.
For this project, we will use the rand crate to select sample values randomly and serde with serde_json to support JSON output later. The Rust random number library provides tools for generating random values, while Serde provides a framework for serializing Rust data structures into formats such as JSON. <Cite refs={[“turn365371search9″,”turn365371search11”]}/>
Add these dependencies to the [dependencies] section of Cargo.toml:
[dependencies]
rand = "0.10"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Cargo will download compatible versions of the libraries when the project is built. If you use different crate versions, check their documentation because some APIs may change between releases.
Creating the Address Data Structure
A good address generator needs a clear way to represent each record. Rust’s struct feature allows us to group related fields together.
Open src/main.rs and begin with the following definition:
use serde::Serialize;
#[derive(Debug, Serialize)]
struct Address {
street: String,
unit: Option<String>,
city: String,
state: String,
zip_code: String,
}
The Address structure contains the main fields that the generator will return. The street field stores the street number and name, while unit is optional because not every address includes an apartment or suite.
The Serialize derive allows the structure to be converted into JSON later. The Debug derive makes it easier to inspect records during development.
Keeping the address information in a dedicated structure is better than assembling a long string immediately. It allows the program to format, validate, display, and export the same record in different ways.
Building the Random Address Generator
Now the project needs the logic that creates a new record. We will use fictional street names and clearly labelled test identifiers. This avoids accidentally presenting random combinations as real postal addresses.
Replace the contents of src/main.rs with the following example:
use rand::Rng;
use serde::Serialize;
#[derive(Debug, Serialize)]
struct Address {
street: String,
unit: Option<String>,
city: String,
state: String,
zip_code: String,
}
fn generate_address() -> Address {
let mut rng = rand::rng();
let streets = [
"Example Creek Drive",
"Sample Meadow Lane",
"Fictional Oak Street",
"Demo Valley Road",
"Test Garden Avenue",
];
let locations = [
("Sampletown", "TX", "TEST-TX-001"),
("Demo City", "CA", "TEST-CA-001"),
("Exampleville", "FL", "TEST-FL-001"),
("Testfield", "OH", "TEST-OH-001"),
];
let street_index = rng.random_range(0..streets.len());
let location_index = rng.random_range(0..locations.len());
let number: u16 = rng.random_range(100..=9999);
let unit = if rng.random_range(0..2) == 1 {
Some(format!("Unit {}", rng.random_range(1..=50)))
} else {
None
};
let (city, state, zip_code) = locations[location_index];
Address {
street: format!("{} {}", number, streets[street_index]),
unit,
city: city.to_string(),
state: state.to_string(),
zip_code: zip_code.to_string(),
}
}
fn main() {
let address = generate_address();
println!("Fictional test address:");
println!("{}", address.street);
if let Some(unit) = &address.unit {
println!("{}", unit);
}
println!(
"{}, {} {}",
address.city, address.state, address.zip_code
);
}
The program starts by creating a random number generator. It then selects a street name and a location from predefined collections. A street number is generated, and an apartment or unit is included in some records.
Notice that each city is stored together with its state and test identifier. This keeps the location fields associated with one another rather than choosing them independently.
The TEST-TX-001 style identifiers are deliberately not presented as real ZIP codes. For realistic postal testing, replace these placeholders only with suitable, permitted reference data and document the source. A five-digit number that looks like a ZIP code is not proof that a location exists.
Formatting the Generated Address
A structured record is useful for software, but users often want to see a readable address. A formatting method keeps this logic separate from the generator.
Add the following implementation beneath the Address structure:
impl Address {
fn formatted(&self) -> String {
let mut output = self.street.clone();
if let Some(unit) = &self.unit {
output.push_str(&format!("\n{}", unit));
}
output.push_str(&format!(
"\n{}, {} {}",
self.city, self.state, self.zip_code
));
output
}
}
You can now simplify the display code in main():
fn main() {
let address = generate_address();
println!("Fictional test address:\n");
println!("{}", address.formatted());
}
This method checks whether a unit exists before adding it to the output. As a result, records without apartment information do not contain an unnecessary blank unit line.
Keeping formatting separate is a small improvement that makes the project easier to maintain. If you later need a single-line format for a spreadsheet, you can add another method without changing the generation logic.
Generating Multiple Test Addresses
The generator currently creates one record per run. In real development work, however, a tester may need twenty or a hundred records to populate a database or check a form under different conditions.
Rust makes batch generation simple. Add a function that calls the existing generator repeatedly:
fn generate_many(count: usize) -> Vec<Address> {
(0..count)
.map(|_| generate_address())
.collect()
}
The function returns a vector containing the requested number of records. You can then update main() to generate ten addresses:
fn main() {
let addresses = generate_many(10);
for (index, address) in addresses.iter().enumerate() {
println!("Test record {}:", index + 1);
println!("{}\n", address.formatted());
}
}
This approach reuses the same generation logic instead of duplicating code. It is also easy to extend with a command-line argument that lets users specify how many records they need.
For a production tool, validate that the requested quantity is within a sensible limit. Generating a few hundred records is usually manageable, but an unlimited request could waste resources or create unnecessarily large output.
Exporting Address Records to JSON
Developers often need test data in a format that other tools can read. JSON is especially useful for API testing, web applications, and development databases.
Because the Address structure already derives Serialize, the serde_json library can convert a collection of records into JSON. Update main() as follows:
fn main() {
let addresses = generate_many(10);
match serde_json::to_string_pretty(&addresses) {
Ok(json) => println!("{}", json),
Err(error) => eprintln!("JSON export failed: {}", error),
}
}
The output will be a JSON array containing the generated records. Each record includes the street, optional unit, city, state, and test identifier.
If you want to save the JSON output to a file, use Rust’s standard file-writing functionality. Make sure the program reports errors if it cannot create or write the file. For larger datasets, consider writing records directly to a file instead of keeping every record in memory.
CSV export is another useful option, especially for teams that prefer spreadsheets. Whichever format you choose, keep the field names consistent and identify the data as fictional test content.
Improving Address Data Quality
A generator becomes more useful when its records follow consistent rules. The first improvement is to maintain the relationship between cities, states, and any postal reference values. Randomly combining unrelated location fields can create records that appear plausible but are internally inconsistent.
The second improvement is to distinguish formatting checks from postal verification. A program can confirm that a state field contains two characters or that a ZIP code field follows a five-digit pattern. Those checks do not establish that the address exists.
The United States Postal Service offers tools for looking up ZIP codes and standardizing domestic mailing addresses. Its official developer portal describes services for address correction, ZIP Code lookup, and city and state lookup. <Cite refs={[“turn365371search3″,”turn365371search8”]}/>
The United States Postal Service is headquartered at 475 L’Enfant Plaza SW, Washington, DC 20260, USA. Its website is https://www.usps.com/, and its developer portal is https://developers.usps.com/.
If a project requires genuine delivery addresses, use an appropriate authorized verification service and follow its terms. Do not assume that fictional output is suitable for live shipping, billing, or identity verification.
Testing the Rust Address Generator
Before expanding the program, test the parts that matter most. Start by running the project:
cargo run
Cargo compiles the program and runs it. If the build fails, read the compiler message carefully. Rust’s compiler often points to the exact line where a type mismatch or syntax problem occurs.
Next, check that every generated record includes a street, city, state, and test identifier. Confirm that the optional unit appears only when it has a value, and verify that batch generation returns the requested number of records.
You can also write unit tests. For example, generate a batch of ten addresses and assert that its length is ten. Check that the state field is one of the states included in the sample location collection. These tests help protect the project as you add features.
For more information about Rust packages, dependencies, and testing workflows, consult the official Rust documentation at https://doc.rust-lang.org/book/ and the Cargo guide at https://doc.rust-lang.org/cargo/.
Common Mistakes to Avoid
One common mistake is assuming that random generation produces genuine postal addresses. It does not. Randomly selecting a street number and street name cannot establish that a property exists.
Another mistake is mixing city and state values without a structured relationship. Keep location information together in a single collection or data structure so the program cannot accidentally combine unrelated values.
Developers should also avoid placing every feature in one large function. Separate the data model, generation logic, formatting, export, and tests. This makes the application easier to read and improves the chances of reusing its components in other projects.
Finally, do not use real residents’ personal information simply to make test data look convincing. Fictional data is often sufficient for testing form behaviour and database workflows. Clearly mark records and prevent them from entering production systems by mistake.
Future Improvements for a US Address Generator in Rust
Once the command-line version works, you can add features gradually. A state selection option would let users focus on one test region. A command-line argument could control the number of generated records, while a format option could select JSON or CSV output.
You could also add a small web interface using a Rust web framework, or expose the generator through an API for use by other testing tools. If you introduce a reference dataset, document its source and licence, and avoid claiming that records are deliverable unless that has been verified.
For applications that require repeatable tests, consider adding a way to use a fixed random seed. This can help developers reproduce the same sequence of generated values when debugging a problem. Keep this feature separate from any security-sensitive randomness requirements.
The best improvements are those that solve a clear testing problem without making the program unnecessarily complicated.
Conclusion
Building a US address generator in Rust is a practical project that teaches important programming concepts, including structs, random selection, vectors, optional values, serialization, and testing.
By starting with fictional sample data, you can create a working generator and gradually add batch generation, formatted output, and JSON export. Rust and Cargo provide a straightforward foundation for organising and maintaining the project.
The most important rule is to keep synthetic test data separate from verified postal information. A generated record may be useful for testing a form, but it is not automatically a real address. With clear labels, consistent data structures, and appropriate validation, your Rust address generator can become a dependable tool for development and quality assurance.
