1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-04-23 16:12:47 +02:00
comprehensive-rust/src/generics/generic-functions.md
Nicole L 7f45460811
Rework generic fn examples to show monomorphized versions (#2671)
Something I always do when covering generic fns is I like to show the
monomorphized versions of `pick` to make it clear to students what
generics are doing behind the scenes. In my most recent class I tried
going the other way around, showing the monomorphized versions first to
more clearly motivate what generics are used for, and I liked the way it
went. I think motivating generics by first showing code duplication and
then showing how generics allow us to de-duplicate makes for a good
teaching flow, and I think it also helps make things clearer to students
coming from more dynamic languages that don't have an equivalent to
generics.

I also changed the `pick` fns to take a `bool` as the first argument
because I think that makes things slightly clearer/cleaner, but I'm not
married to that change either.
2025-03-03 10:03:10 -05:00

1.9 KiB

minutes
minutes
5

Generic Functions

Rust supports generics, which lets you abstract algorithms or data structures (such as sorting or a binary tree) over the types used or stored.

fn pick<T>(cond: bool, left: T, right: T) -> T {
    if cond {
        left
    } else {
        right
    }
}

fn main() {
    println!("picked a number: {:?}", pick(true, 222, 333));
    println!("picked a string: {:?}", pick(false, 'L', 'R'));
}
  • It can be helpful to show the monomorphized versions of pick, either before talking about the generic pick in order to show how generics can reduce code duplication, or after talking about generics to show how monomorphization works.

    fn pick_i32(cond: bool, left: i32, right: i32) -> i32 {
        if cond {
            left
        } else {
            right
        }
    }
    
    fn pick_char(cond: bool, left: char, right: char) -> char {
        if cond {
            left
        } else {
            right
        }
    }
    
  • Rust infers a type for T based on the types of the arguments and return value.

  • In this example we only use the primitive types i32 and char for T, but we can use any type here, including user-defined types:

    struct Foo {
        val: u8,
    }
    
    pick(123, Foo { val: 7 }, Foo { val: 456 });
    
  • This is similar to C++ templates, but Rust partially compiles the generic function immediately, so that function must be valid for all types matching the constraints. For example, try modifying pick to return even + odd if n == 0. Even if only the pick instantiation with integers is used, Rust still considers it invalid. C++ would let you do this.

  • Generic code is turned into non-generic code based on the call sites. This is a zero-cost abstraction: you get exactly the same result as if you had hand-coded the data structures without the abstraction.