Generics, Maybe one type, Maybe another type
So Rust needs to have a concrete type for what a function takes in and what it might spew out(input, output). Many times we want to create a function that might be able to take in different types and gives result of different types as well.
fn some_func(input: i32) -> i32 {
return input;
}
The function some_func
will only ever allow a type i32
as input and will always return a type i32
as output, anything else will result in an error.
So, why do we need a Generic?
We need to be able to create a function and tell it, i want to be able to give you "these" types as input and you give me "these" types as output.
Basically we want to avoid doing this:
fn some_func(input: u16) -> u16{}
fn some_func(input: i16) -> i16{}
fn some_func(input: &str) -> &str{}
Instead Generics enables us to fabricate our own type and Rust will make it a concrete type adhering to the rules.
Generic Function
fn some_func<T>(input: T) -> T{
// some operation
}
The letter or word and the angle brackets(<T>
) signifies to Rust that we are creating a generic and then in the parameter brackets Rust now sees T
as a concrete type.
Without <T>
, Rust will still see T
as a concrete type and then throw an error as it won't find it among it's list of concrete types, String
, &str
, i32
etc.
It's popular convention among Rust developers to give generics single letter to denote generics but it can be anything, a letter or a word. IT could have been <generic>
, MYType
or simply <S>
.
We might pass in a type that doesn't implement Display
, Debug
or other traits and the compiler will error, we need to tell Rust "Hey, I'm sure this type will have certain traits".
use std::fmt::Debug;
fn some_fun<T: Debug>(input: T){
println!("{:?}", input)
}
Now, we can print input
with the Debug
trait.
Note that we can't trick the compiler(LOL), Whatever input must implement the Debug
trait in this case.
We might need more than one generic type in a function we can also specify that.
Let's say we need our function to be able to take in a type that can be printed out with {}
"Display
" trait and also perform comparison operation such as < > <= ==
- "PartialOrd
" trait.
use std::fmt::Display;
use std::cmp::PartialOrd;
fn some_func<T: Display, C: Display + PartialOrd>(text: T, input_1: C, input_2: C){
println!("{text}, Is {input_1} less than {input_2}, the same {}", input_1 < input_2)
}
fn main(){
let call_out = "Hear me out!";
let a = 34;
let b = 64;
some_func(call_out, a, b);
}
it will print out:
Hear me out!, Is 34 less than 64 ? true
Now variable text
can be anything with Display
and input_1 input_2
anything with Display
and PartialOrd
.
We can make generic function more readable
Instead of the above we can have.
fn some_func<T,C>(text: T, input_1: C, input_2: C)
where{
T: Display,
U: Display + PartialOrd,
}
{
//function body
}
N/B: Using where
is good when there too many generic types.