- In this function,
we're going to see how and why to define generic functions.
So first of all, let's see the need for generic functions.
Have a look at this function.
It takes an array slice, an array of 32 bit integers
and it prints out the length of the array,
how many elements it contains
and also prints out the size of each element.
Well, each element is gonna be a 32 bit integer
so it'll tell me the size of each 32 bit integer.
Well, 32 bits, that would be four bytes.
Remember, the standard crate has a mem module
and the mem module, which is like a namespace
has a size of function, which is generic.
You tell it the type,
and it'll tell you the size of that type in bytes,
four bytes.
Okay?
Now have a look at this function.
This one is similar, but this one takes an f64 slice.
But apart from that, it's the same.
The only difference between the functions
is the type involved.
This function deals with f64.
It'll tell me the length of the array in elements
and the size of each element,
each element being an f64.
So if you look at this function
and this one, the only difference
is that it deals with int 32 here and f64 here.
Don't ever define multiple functions
which are identical apart from the type, okay.
Because you'll have duplicate code,
which you have to maintain separately
and eventually they'll drift out of sync.
And it's not exhaustive.
I've written a function to display information
about an array of integers and floats.
But what about an array of bank accounts
or an array of customers?
Okay, you'd have to write a separate function
display array customer, display array employee.
So clearly this approach having separate functions
each type isn't right, is it?
So in that case, we can define a generic function instead.
And obviously the idea is
instead of specifying a specific type,
you use a placeholder type instead.
So this is a generic function.
After the name of the function, you have angle brackets
and then you have some type prompter here.
I've called it T.
Obviously you can call it anything you like.
You can have multiple type prompters as well,
you'd say like T1 comma, T2 comma, T3.
And then the function receives an array slice of Ts.
Obviously the compiler doesn't know what T is.
When you call it, you'll actually specify an array
and at that point it'll know
what type of array you've passed in.
But in general, the algorithm's the same.
Whatever type it is, the code is the same.
We just use T here instead i32 or f64.
Okay?
So the code here is now generic.
It can be cut out and duplicated
for whatever type of T I pass in.
So if you have a generic function, how do you call it?
Okay, so you can call the generic function
explicitly specify the type like so.
I've got an array of integers.
I call it ai, array of integers.
I've got an array of floats, af.
I call my generic function passing in a type parameter.
So T in this case would be i32
and I've given it a reference to an i32 array.
And then I call the function a second time.
And this time I pass in a different type parameter, f64
and I give it an array size of 64 bit floats.
Okay, so you can tell the compiler what type
to substitute T in the function T
is going to be i32 or T is going to be f64.
In most cases, if the parameter you're passing in,
if the type parameter appears in the incoming signature
of the function,
the compiler can look at the parameters you passed in,
and guess what type you mean.
Okay, so I can simplify the calls here
to the way I've called them here
without specifying the type parameter.
The compiler, it knows that it's a generic function
and it knows at the back of its mind
that it has to figure out what type T is.
But the compiler also knows
that the function receives an array of T.
And if it can see the array passed in is int 32
then it'll reverse engineer and assume
that the type T is i32.
And the type T here is f64.
So if the type parameter T appears as one of the parameters
when you pass in a parameter
that will basically tell the compiler
what type T is gonna be for that particular call
i32 and then f64.
This syntax here is clearly more straightforward
on how to explicitly specify the type here.
So this would be the preferred approach.
Right, well, I'm gonna run an example of that.
Lesson13 generics.
We'll run the main code to entry point
for the application and then demo generic functions
will be where all the action lives and then we'll run it.
Okay, so my source code in main.
let's uncomment the code for demo generic functions.
So we can run that.
And here is the module demo generic functions.
And what I've done, as per the slides,
I've got three different functions.
I've got a function which only deals
with 32 bit integers.
Okay, and we've seen that code.
And then I've got a function which only deals
with 64 bit floats or arrays of, and we've seen that code.
And as mentioned, if you look carefully
the only difference between the functions
is the type here and here
compared to the type here and here.
So in reality,
you wouldn't bother defining those two functions.
You would instead define a single generic function
process array where T is a type parameter,
it's an array of some type T.
When the function is called, it'll become apparent.
And at this point here, you know,
it needs to know what type T is gonna be
when you call the function.
So in my code up here
I go through the steps just to be complete.
First of all, I call a function hardcoded
to receive an array of i32s process array ints.
That's the first function.
And then for due diligence
I then call a function that's hardcoded
to receive an array f64, process array floats.
So that's this function.
And then I call my generic function using explicit type int
for i32 and f64.
Notice that I've got an array of i32 and an array of f64.
So I call the generic function using explicit syntax
to show that you can.
And then I call a function using the implicit syntax.
It'll guess that I, sorry, T is i32 there
and it'll guess that T is f64 here.
So we'll just run it.
Cargo run.
Okay, so that's my array of i32 and my array of f64.
There were five elements in my integer array.
And each element was an i32, four bytes in size.
In my floated point array, I only had three elements.
So when I passed in, when I called my demo array floats
there were three elements and each element was a 64 bit
or eight byte float, an f64 in other words.
And then I called my generic functions
to display the array of integers and floats.
And then I called my generic functions again
using implicit type int to show the syntax.
Okay, so in all of this, if this was real code
then I wouldn't bother writing that function.
It's gone.
I wouldn't bother have written that function.
It's gone.
I'd have just had a single function process array.
And then up here, I'm just gonna peel back my demo.
I wouldn't call the process array int function,
I'd call process array and I'd pass in my array like so
and I'd call my generic function process array like that.
Okay.
And then I guess I could just get rid of the other demo code
because that would be sufficient.
Basically create an array of integers.
The compiler knows what type T is going to be, i32.
Create the array of 64 bit floats.
The compiler knows that T in this case is going to be f64.
So quite straightforward.
I guess there are one or two differences.
The way that you specify the generic type when you call it.
Let me just revert all my code just for old times sake.
I think the only thing that you probably wouldn't expect
from a different programming point of view
is when you do pass in the type parameter explicitly
you have to use these colons before the type name.
So other developers from other languages
might be tempted to do that.
That syntax really doesn't work.
Let me just show you the error messages you get back there.
Yes.
So looking at line 14,
on line 14 when it sees the angle brackets there,
it's kind of scratched its head a little bit and said
did you mean colon colon angle brackets?
Yes, I did.
What a good compiler you are.
I need to put the double colons back in.
Okay, and that really is just something
you need to watch out for when you're getting used to Rust.