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.
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 genericpick
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
andchar
forT
, 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 returneven + odd
ifn == 0
. Even if only thepick
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.