Generic Functions
Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 27 of 61.
Generics aren't limited to structs—you can also write standalone functions that work with any type. This is useful when you need a utility function that doesn't belong to a specific struct but should still be flexible.
The syntax mirrors what you've seen with structs. Declare the generic parameter in angle brackets after the function name, then use it in the parameters and return type:
fn identity<T>(value: T) -> T {
value
}
This identity function takes a value of any type and returns it unchanged. The <T> declares the generic, value: T accepts it as a parameter, and -> T specifies the return type. When you call the function, Rust infers the concrete type:
let num = identity(42); // T is i32
let text = identity("hello"); // T is &str
You can also use multiple generic parameters in functions, just like with structs:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // returns (i32, &str)
Generic functions let you write reusable logic once and apply it to many types, reducing code duplication while maintaining type safety.
Challenge
EasyLet's build a utility module with generic functions that can work with any type! You'll create standalone functions that demonstrate how generics make your code flexible and reusable without being tied to a specific struct.
You'll organize your code across two files:
utils.rs: Create a collection of public generic utility functions:wrap_in_pair<T>— takes a single value and returns a tuple containing that value twice:(value, value). This requires the type to beClone, so use<T: Clone>swap<T, U>— takes two values of potentially different types and returns them in reversed order as a tuple(U, T)
main.rs: Import your utility module and demonstrate these generic functions working with different types. Show how the same function definitions handle integers, floats, and strings seamlessly.
In your main file, demonstrate your utility functions by:
- Using
wrap_in_pairwith an integer (first input, parsed asi32) and printing both elements - Using
wrap_in_pairwith a string (second input) and printing both elements - Using
swapwith an integer (third input, parsed asi32) and a string (fourth input), then printing the swapped result
Your output should follow this format:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})For example, with inputs 5, hello, 42, and world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Notice how wrap_in_pair works identically for both integers and strings, and swap handles two completely different types—that's the flexibility of generic functions!
You will receive four inputs: an integer, a string, another integer, and another string.
Cheat sheet
Generic functions allow you to write standalone utility functions that work with any type. Declare the generic parameter in angle brackets after the function name:
fn identity<T>(value: T) -> T {
value
}
The <T> declares the generic type parameter, which can then be used in function parameters and return types. Rust infers the concrete type when you call the function:
let num = identity(42); // T is i32
let text = identity("hello"); // T is &str
You can use multiple generic parameters in a single function:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // returns (i32, &str)
When a generic type needs specific capabilities, use trait bounds:
fn duplicate<T: Clone>(value: T) -> (T, T) {
(value.clone(), value)
}
Try it yourself
mod utils;
use std::io;
fn main() {
// Read the four inputs
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Use wrap_in_pair with num1 and print the result
// Format: Pair of ints: ({value}, {value})
// TODO: Use wrap_in_pair with str1 and print the result
// Format: Pair of strings: ({value}, {value})
// TODO: Use swap with num2 and str2, then print the swapped result
// Format: Swapped: ({string}, {int})
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Methods and Behavior
Intro Implementation BlocksThe Self ParameterMutable MethodsAssociated FunctionsMultiple Implementation BlocksMethod ChainingRecap - Rectangle Actions4Project: Virtual Pet
Defining the PetFeeding the Pet7Standard Traits
The Debug TraitThe Display TraitClone and CopyEquality TraitsRecap - Printable Point10Project: Document System
The Draw TraitText Component2Encapsulation and Modules
Modules BasicsThe Public KeywordPrivate FieldsGettersSettersRecap - Secure Locker5Generics
Generic StructsGeneric MethodsMultiple Generic TypesGeneric FunctionsRecap - Coordinate Point8Traits as Bounds
Trait Bounds SyntaxMultiple BoundsThe Where ClauseReturning Types with TraitsRecap - Generic Printer11Design Patterns in Rust
Newtype PatternCompositionThe Drop TraitFrom and IntoRecap - Smart Pointer Mock