[No Audio]
This tutorial is about Generics. In the
simplest definition, generic programming is a
style of computer programming in which
programs are written in terms of to be
specified and later types. We do not specify
the types and keep them generic, and later on,
they're defined based on the data they receive.
All this may seem a bit superficial
for now, but it will start to make sense once
we have some examples. Before jumping into
the concepts and details of generics, I will
start first with a motivating example as to
why we need to use generics. And when they
will be useful for us, please remember that I
will give you a surprise at the end of the
tutorial with regards to generics. For that
you have to stay and wait till the end of the tutorial.
So let's start learning about generics.
Suppose we want to have a function
for computing the square of a given number.
Let me define this function. This is a very
simple implementation of the square function
which will accept an integer, and we'll
compute it square and will return an integer answer.
Let's use this function in the main,
[No Audio]
when we execute, you may note that this
program is working fine. Now, what if I am
interested in finding the square of a float
number. I cannot use the square function
because it uses an i32 type. Of course, I
can write something like number is i32.
But again, it will not compute the exact
square, but rather will truncate the fraction
part, from the number and then it will compute
it square. The only way out will be to write
another function for the square which will
accept a float number and will compute its
respective square. So, let me define another
function for the float type.
[No Audio]
I want to differentiate the square for i32
from that of f32. Therefore, I have to
name it differently. Let us use this function now.
[No Audio]
This was simple function therefore, the code
duplication may not be that much significant.
Consider the case where the function is quite
complex and contains many lines of code and
involves heavy computation. As we will be
writing the code with lots and lots of
duplication in that case, the generics comes
into play here, and it allows us to write code
by minimizing code duplication.
So, let us see the details but first, we will look at
the general syntax.
[No Audio]
To declare a generic
function, I will define the function as usual.
After the function name, I will
mention the input to be of type generic.
To mention this, I will include less than and
greater than symbols and inside that I will
include the name of the generic variable that
I want to use, I can use any name but the
convention is to use the letter T. Next, I
will mention that the input will be of
generic type T by mentioning inside the
parentheses, the type of variable x to be that of T.
For the output of the function, I will
use the same generic type inside the body of
the function I will include the code
for computing the square, let us use it in
the main now.
[No Audio]
I will call it for integer
input as well as for the float input inside
the print statement.
[No Audio]
It seems that the compiler has something to
say to us. It says cannot multiply T by T.
Since T can be anything that is it can be
string or char or integer or float or
unsigned. Therefore, the Rust cannot do the
multiplication in all possible cases. In some
sense, we need to give the compiler a little
bit more information about the possible type
the generic T can be. This additional
information is after the colon, we will
mention a couple of case for the type T which
corresponds to multiplication and copy.
[No Audio]
Okay now what does it mean? It basically
tells the Rust compiler that I am using a
generic which is restricted to any type which
implements the trait of multiplication and copy.
Please note that, we discussed in quite
some detail the difference between copy and move.
The primitive types are copied and not
moved. So this means that, the type T which is
generic, but it does not mean that it can be
anything we are rather restricting it to
types that have the multiplication trait and
the copy trait. The output in front of the
multiplication indicates to it that the
result of the multiplication should result in
a value of type T, that is the generic type.
Once we write the proper restrictions, the
compiler is happy. Please note that the
restrictions depend, on the operations we
intend to perform on generic. For instance,
if we change the multiplication operation to
that of addition, we will need to mention
something else here. The Rust compiler will
always gives us useful hints, if we move our
cursor, in this case you may see relevant hints.
Let us change back.
[No Audio]
We will execute and you may note that, the function is
executed successfully for both the integer
and float values without the need for code
duplication. You may be thinking that, that
it may be very hard to remember
all the restrictions that we may have when
using the generics. Well, this is not that difficult.
If for instance, I forget the
trait of copy, let me delete it from here.
Now, when I execute again
[No Audio]
The Rust compiler
will tell me exactly what is missing by
highlighting it in green text.
[No Audio]
In case if I forget the first one, which is a bit long
too, I will delete it and cargo run again.
[No Audio]
The Rust will give me the exact syntax which
I need to include. Sometimes this list that
we mentioned to restrict the generic may get
too long. For instance, if I add one more
operation inside the function by writing x + x
and execute again.
The Rust will
require one more item here in the list of
four addition which is being shown in the
green text color. Let me copy and add to the list.
[No Audio]
You may note that the list is
getting too big, and it may be difficult to
read down the function definition itself. Let
us provide an alternate syntax for such cases.
We will get all the different traits
information from the list here and will only
keep T. After mentioning the inputs and
outputs. We will move to the next line and
we'll include the where keyword followed by the
complete list. After the list we will start
with the body of the function this syntax is
more readable and more understandable.
[No Audio]
Let us learn how to use structures
in connection with generics.
[No Audio]
Consider a simple structure
which defines a point, a point is defined by
two values one for its location on the x axis
coordinate and another one for specifying its
location on the y axis coordinate. So let us
say 2 fields to the structure definition.
[No Audio]
Let us use this structure in the main to
define some points. I will define point p1
with coordinates having integer values of 5 and 5.
[No Audio]
Now what if I want to create a
point with floating values for its x and y
coordinates. For instance, I will have a
point p2 with coordinates of 1.0 and 4.0.
[No Audio]
The compiler will rightfully
complained in this case, you may note that we
may need some code duplication for
declaring another struct with fields of float
instead of integers. Again, it's anticipated
the generics will help us in this case, we
may define the fields of the structure as
generic instead of some fixed type. I will go
ahead and change the definition of the
structure by first indicating after the name
of the structure, that it will use a generic T
inside the definition of the structure I will
change the type for both the fields to that
of T you may notice the compiler is not
complaining anymore. Okay great so far. Let
me make it a bit more interesting for now, I
will define another point p3 A with x
value being integer and y value being float.
[No Audio]
In this case, now one of the fields is
integer but the other one is that of float, the
compiler is not happy again. This is because
both the fields are of the same generic type.
And once the compiler sees the field of x to
be of integer for a certain instance of the
struct, then it fixes the type of T to that of
integer for that particular instance.
Therefore, it expects the field of y to be of
integer also, for the same instance of the struct.
To allow the two types to be
different, we may introduce another generic
field, let's say the generic of you. So in
the structure definition, I will add another
generic cue. Now I will change the type of
field y to that of u.
You may not now
that the compiler is now happy. Also please
note that in case of first point the Rust
compiler considers both the fields as i32.
In case of p2 the Rust compiler
considers both the fields to be of f64.
In case of p3 however, one of the fields is
considered to be i32 and the other
one is f64. All this is possible due to use
of generics. The point to note is that the
type of the generic will be deduced by the
compiler for each instance of the struct
separately. Okay, now let us look at how we
can implement some functions for the
structure with generics. Suppose I want to
define a function on the point which will
print its x and y fields. This is going to be
very similar to the way we usually define the
implementation block of a struct. So let me
include an implementation block. After the
impl, I will mention the list of generics
inside the less than greater than since there
are two generics in this case, so I will
include their names after the name of the
struct, which is point, I will indicate that
there are two generics with regards to the
point structure.
[No Audio]
Next, I will add a print function and we'll
add a print statement to add for printing the
x and y values.
[No Audio]
You may note that there is
some remaining issues that needs to be
resolved the compiler, the compiler needs
some trait information, let us cargo run this to
see what are the exact traits that I need to include.
[No Audio]
I have the required information in green
color, so I will copy and we'll paste it just
after the impl as indicated in the in our error message.
Please note that as soon as I
include it for generic use the error message
for the self.y disappears. This means
that I need to include the same for the
generic type T.
[No Audio]
Everything is fine. Now, I
will call this function from the main now for point p1.
[No Audio]
Let us execute. It works fine, that's great.
Again, please note that there
is an alternate and more cleaner way of
writing the additional traits by deleting all
the information in this list from here and
paste it separately, with the where
keyword just before the body of the
implementation block. Now as mentioned at the
beginning, I was supposed to give you a
surprise so it's time for that, we have been
using the generic from the very start of the
course. In fact, the vectors basically uses
the generics, the syntax for vectors is vec
and then within the less than greater than
the type information. The type of the vector
is a generic with the type information being
set by us when we declare a vec. This
means that generic is our old friend. Okay
with this, we end this tutorial. In the next
tutorial we will be learning about the Option
enum, which will help us to understand
further the topic of generics and the topic
of enums. That will be fun because we will be
looking at the concept of enums and generics
and action at the same time. Do come back for
learning that and until then, happy Rust programming.
[No Audio]