Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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 be Clone, 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:

  1. Using wrap_in_pair with an integer (first input, parsed as i32) and printing both elements
  2. Using wrap_in_pair with a string (second input) and printing both elements
  3. Using swap with an integer (third input, parsed as i32) 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})
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming