<ao> | Adetunji's Blog

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.

#note #rust