CodeBlanks Logo Icon CodeBlanks

Monomorphization

Learn the cost of generics through monomorphization.

Monomorphization

A generic function is compiled separately for each concrete type. Each copy is fully optimized for that type; the price is code bloat and longer compiles:

fn tanim<T: std::fmt::Debug>(x: T) -> String {
    format!("{x:?}")
}

tanim(5);          // tanim::<i32>
tanim("a");        // tanim::<&str>
tanim(vec![1, 2]); // tanim::<Vec<i32>>  → three separate functions

A common way to reduce code bloat in a large generic function is moving the type-independent part into a non-generic inner function.

The inner function pattern

The generic entry is kept thin; the real work happens in a single concrete function:

pub fn oku<P: AsRef<std::path::Path>>(yol: P) -> std::io::Result<String> {
    fn ic(yol: &std::path::Path) -> std::io::Result<String> {
        // a long, type-independent body
        std::fs::read_to_string(yol)
    }
    ic(yol.as_ref())
}

An alternative: using &dyn Trait on large, rarely called code paths. The cargo bloat tool shows which functions take the most room in the binary.

Practice Three copies

Write the generic tanim function above. Print the results for 5, "a" and vec![1, 2] separated by | .

Starter code

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
fn tanim<T: std::fmt::Debug>(x: T) -> String {
    format!("{x:?}")
}

fn main() {
    println!("{} | {} | {}", tanim(5), tanim("a"), tanim(vec![1, 2]));
}

Expected output

5 | "a" | [1, 2]

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why does the standard library's <code>fs::read_to_string&lt;P: AsRef&lt;Path&gt;&gt;</code> call a non-generic function inside?

  • For speed
  • For safety
  • Convention
  • So the real work isn't compiled again for every P type Correct answer

Explanation: The outer generic layer only calls as_ref(); the body stays in a single copy.

2 Shared body

Question
pub fn oku<P: AsRef<Path>>(yol: P) -> io::Result<String> {
    fn ic(yol: &Path) -> io::Result<String> { fs::read_to_string(yol) }
    ic(yol.____())
}
Answer
pub fn oku<P: AsRef<Path>>(yol: P) -> io::Result<String> {
    fn ic(yol: &Path) -> io::Result<String> { fs::read_to_string(yol) }
    ic(yol.as_ref())
}

Explanation: as_ref turns the generic P into &Path; ic is compiled once.

3 Call and generated copy

Term Meaning
tanim(5) tanim::<i32>
tanim("a") tanim::<&str>
tanim(vec![1, 2]) tanim::<Vec<i32>>
tanim(7) No new copy (i32 exists)

All lessons in Expert