Introduction
Concrete types are useful when data has one fixed shape, but repeating nearly identical types for numbers and text makes a program harder to maintain. You have already used standard generic types such as Option<u32>, Vec<String>, HashMap<String, u32>, and Result<T, E>. In this Lab, you will define a generic type yourself. Rust generics let one definition work with several concrete types while the compiler still checks every use.
In this lab, you will make a prepared Pair structure generic and then generalize a small borrowed helper. The longer program and sample data are already written, so your work stays focused on the few type positions that create reuse.
Make the Pair Structure Generic
In this step, you will replace the structure’s fixed i32 type with one type parameter so the same definition can hold either scores or route names.
Move into the prepared Cargo package:
cd /home/labex/project/pair-demo
Inspect the short source file. The sed -n command prints the requested line range without changing the file:
sed -n '1,120p' src/main.rs
The scores value contains integers, while routes contains owned String values. The current Pair definition fixes both fields to i32, so it cannot represent both values. The prepared #[derive(Debug)] attribute would generate debug formatting for the type, but the visible program does not use that formatting; it is not part of the generic task.
A generic type introduces a placeholder such as T inside angle brackets. Each constructed Pair still has one concrete type: the compiler infers Pair<i32> for scores and Pair<String> for routes.
Open the source in Nano:
nano src/main.rs
Make these three small changes near the top of the file:
- Change
struct Pair {tostruct Pair<T> {. - Change the type of
leftfromi32toT. - Change the type of
rightfromi32toT.
The completed structure is:
struct Pair<T> {
left: T,
right: T,
}
Here, both fields use the same T, so one pair cannot accidentally mix unrelated field types. Save with Ctrl+O, press Enter, and exit with Ctrl+X.
Ask Cargo to type-check the package:
cargo check
A line ending in Finished means the compiler accepted both Pair<i32> and Pair<String>. Now run the program:
cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82
The first two lines prove that one structure definition now stores two different concrete types. The existing first helper still accepts only Pair<i32>, which you will generalize next.
Generalize a Borrowed Helper
In this step, you will give the first function its own type parameter so it can borrow any Pair<T> and return a reference to its first value.
Inspect the current helper near the top of the file:
sed -n '1,30p' src/main.rs
Its Pair<i32> input and &i32 output make it specific to scores. A generic function declares <T> after its name, then uses that same T wherever the related input and output types appear.
Open the file:
nano src/main.rs
Change only the function declaration from:
fn first(pair: &Pair<i32>) -> &i32 {
to:
fn first<T>(pair: &Pair<T>) -> &T {
The function borrows the pair and returns a borrowed field, so it neither moves nor copies the stored value. Rust can infer T from each argument; callers do not need to spell out the type.
Next, replace // FIRST_ROUTE_REPORT with one call that proves the helper also works for text:
println!("first route: {}", first(&routes));
Save and exit Nano. Then type-check and run the completed program:
cargo check
cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82
first route: north
The two first lines come from the same generic function. Rust infers T = i32 for the score call and T = String for the route call, then checks each call with that concrete type. Reuse does not give up static type checking.
Summary
You replaced one concrete pair with Pair<T>, used it with both integers and strings, and generalized a borrowed helper without introducing traits or duplicating implementations.


