Menu
Coddy logo textTech

Generic Structs

Part of the Object Oriented Programming section of Coddy's Rust journey — lesson 24 of 61.

So far, every struct you've created has had fields with specific, concrete types. A Pet has a String name, a Rectangle has f64 dimensions. But what if you want to create a struct that can hold any type of data?

This is where generics come in. Generics let you write flexible, reusable code that works with multiple types. Instead of hardcoding a specific type, you use a placeholder—typically T—that gets replaced with a concrete type when you use the struct.

Here's a simple wrapper struct that can hold any type:

struct Wrapper<T> {
    value: T,
}

The <T> after the struct name declares a generic type parameter. Inside the struct, T acts as a stand-in for whatever type you'll actually use. When you create an instance, Rust infers the concrete type:

let int_wrapper = Wrapper { value: 42 };        // T is i32
let float_wrapper = Wrapper { value: 3.14 };    // T is f64
let text_wrapper = Wrapper { value: "hello" };  // T is &str

Each of these is a different concrete type—Wrapper<i32>, Wrapper<f64>, and Wrapper<&str>—but they all share the same struct definition. This eliminates the need to write separate IntWrapper, FloatWrapper, and StringWrapper structs that do essentially the same thing.

The letter T is just a convention (short for "Type"). You could use any valid identifier, but T is standard in Rust and most other languages with generics.

challenge icon

Challenge

Easy

Let's build a generic container that can hold any type of value! You'll create a Container struct that demonstrates how generics make your code flexible and reusable.

You'll organize your code across two files:

  • container.rs: Define a public generic struct called Container<T> with a single public field item of type T. This container should be able to hold any type—integers, floats, strings, or anything else.
  • main.rs: Bring in your container module and demonstrate its flexibility by creating containers that hold different types of data. You'll create three containers and display what each one holds.

In your main file, create the following containers:

  1. A container holding an integer value (the first input, parsed as i32)
  2. A container holding a float value (the second input, parsed as f64)
  3. A container holding a string (the third input, kept as a String)

Your output should display each container's contents in this format:

Integer container: {value}
Float container: {value}
String container: {value}

For example, with inputs 42, 3.14, and hello:

Integer container: 42
Float container: 3.14
String container: hello

Notice how the same Container<T> definition works for all three different types—that's the power of generics!

You will receive three inputs: an integer value, a float value, and a string value.

Cheat sheet

Generics allow you to create structs that can hold any type of data using a type parameter placeholder.

To declare a generic struct, use angle brackets with a type parameter (conventionally T):

struct Wrapper<T> {
    value: T,
}

The <T> declares a generic type parameter, and T inside the struct acts as a placeholder for the actual type.

When creating instances, Rust infers the concrete type:

let int_wrapper = Wrapper { value: 42 };        // T is i32
let float_wrapper = Wrapper { value: 3.14 };    // T is f64
let text_wrapper = Wrapper { value: "hello" };  // T is &str

Each instance becomes a different concrete type (Wrapper<i32>, Wrapper<f64>, Wrapper<&str>) while sharing the same struct definition.

Try it yourself

mod container;

use container::Container;

fn main() {
    // Read the three inputs
    let mut input1 = String::new();
    std::io::stdin().read_line(&mut input1).expect("Failed to read line");
    let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");

    let mut input2 = String::new();
    std::io::stdin().read_line(&mut input2).expect("Failed to read line");
    let float_value: f64 = input2.trim().parse().expect("Failed to parse float");

    let mut input3 = String::new();
    std::io::stdin().read_line(&mut input3).expect("Failed to read line");
    let string_value = input3.trim().to_string();

    // TODO: Create three containers using Container<T>
    // 1. An integer container holding int_value
    // 2. A float container holding float_value
    // 3. A string container holding string_value

    // TODO: Print each container's contents in the required format
    // Integer container: {value}
    // Float container: {value}
    // String container: {value}
}
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