- So far we've seen how to define
simple methods in a structure.
Here's an example.
I've got a public structure called Point.
I've defined the implementation block
and I've defined a public function.
It receives a self-reference to indicate
which point it's dealing with,
and then it prints out the X and the Y coordinates
of that point.
So, in your code, if you had a point object,
let's say p1, you'd call the print function,
like so, the compiler internally will convert it
into a call like this print.
It'll pass in a reference to p1 implicitly
and that reference gets passed in to the self parameter.
So the self parameter, a reference,
it knows which object's data it's dealing with.
It's gonna print out the X and the Y coordinates of p1.
So these are called instance methods.
They receive a self parameter
and the self parameter is a reference
to the target object, p1, in this example.
It allows the instance method
to access the state and the target object.
So when we say self.x and self.y,
it knows, the self prompt, that knows
that it's dealing with p1.
And it's going to be the X and Y coordinates of p1
that get displayed.
Okay? So that's quite straightforward.
It's also possible to define
what are called associated functions.
That's a Rust term.
An associated function in Rust is basically the same
as a static method in other object oriented languages.
So an associated function, you define it
in the implementation block, it's still kind of
part of your structure,
part of the concept of your structure
but the function doesn't receive a self parameter.
Okay. And that means it can't access information
in p1 or p2.
It doesn't know anything about any particular instance.
All it knows about is the structure itself.
So associated functions define functionality that pertains
to the whole structure rather than to a particular instance,
like a static method in other OO languages.
Right, well here's an example of how to define
an associated function.
This is actually quite a realistic example.
So I've defined in my types,
I've got a file called Point 3D.rs.
I define a 3D point with an X, Y, Z coordinate.
And it's public. Notice that the fields aren't public now,
that's interesting.
In my implementation block for Point 3D
I've got a public function called new.
There's nothing magical about the new name here,
in Java, and C Sharp, et cetera,
new is a keyword.
In Rust, it isn't.
It's just by convention.
When you write a function to create an instance
and to initialize it, you tend by convention you tend to
name that method new, but it's just a method name.
You could choose any, you could have called
this method name, give me an instance,
and that would've been fine,
but I called it new by convention.
Note this, that it doesn't take a self parameter, okay?
So when you have a method or an associated function
that doesn't take a self parameter
you don't invoke it on an instance, you basically invoke it
on the structure name and we'll do that shortly.
Anyway, it doesn't have a self parameter
but it does have three other parameters.
And what it does is basically a factory,
when you call this method and you pass in some values
it will return an instance of my type, Point 3D,
and that's what it does.
It creates a new Point 3D, and it initializes the X property
with the value of the X parameter
and the Y and the Z, and it returns that Point 3D.
It basically moves ownership
of that Point 3D back into your client code.
So the client code could call this function
to get back an initialized Point 3D object.
So the client code would look like this.
I've imported the structure name, so I've imported
from the top level namespace in my crate.
There's a module called by types
with a submodule called Point 3D
which defines a structure type called Point 3D.
I mean it looks a little bit long-winded
but that is generally what you would do.
So it knows about the structure,
I invoke upon the structure name.
I invoke the associated function.
Very similar to other languages, in C++,
you say class name, colon, colon,
and then the name of the static method you want to invoke.
If this was Java, you'd say class name dot.
And then the name of the static method
you want to call.
The name of the method happens to be new.
But that's just because I chose to call it that.
It's not a keyword, it's up to you what you call the method.
But we've seen this a lot already in Rust,
where you've got some kind of structure,
and you say colon, colon, new,
it creates a new instance based on the data you pass in
it'll give you back a object with those fields set up.
So p1 is a multiple point initially
with those values, 10, 20, 30.
So some points to note,
Point 3D new, we call it a factory function
because when you call that method, it creates an instance,
returns it back to you.
You and your client code receive ownership
of that object that's been returned.
So technically speaking, what happened,
was when you call the new function
it created an object on the stack.
It then moved that object into our local function here
and that move ,it moved ownership of the object to p1.
Okay, so effectively p1 is the object
that's been created and returned.
Right, well, let's have a look at the example
in lesson11_structs_functionality.
From the top, we'll have main.rs,
which imports our mytypes module.
In the mytypes module, I declare three sub modules,
one of which is point3d.
And in there I define my Point 3D structure.
And then I use it in this file here.
So we'll have a look at the code
and then we'll run it at the end.
Let's have a look.
Right, so from the top, define a module called mytypes.
Mytypes defines submodules.
I should click here actually.
It defines a submodule called point3d.
Okay? So if we're in a file called mytypes
and it defines a submodule,
it'll go to the mytypes subdirectory
and in there it'll find point3d.rs.
Here it is.
So it defines my 3D point structure.
I think this is pretty much the same
as what we've seen before.
Let me just run through it.
A 3D point with private fields
that means we're not accessing those fields externally.
And we have an instance method
which takes a multiple reference to self, and prints it.
We have another instance method, which takes an immutable,
that's an immutable reference to self.
This one receives an immutable reference
and returns a string, a formatted string.
And we've seen these before.
Another instance method that receives a self parameter.
This is a mutable method.
It'll reset the coordinate to an origin, basically zero.
A move by function.
That's also an instance method.
It has a self parameter.
It can mutate the object with deltas.
So it'll offset the X and the Y and the Z
by the specified delta.
Another instance method,
most of the methods you have in the class
are instance methods.
They work on an actual instance.
So the log method, we've seen that already.
It'll take an immutable reference to self
and it'll return a nicely formatted string
with a timestamp.
And then finally at the bottom here
is our associated function.
So in order to differentiate an associated function
from a regular method, it's the absence of a self parameter.
That's the only difference.
It doesn't have a self parameter.
It can't access anything on the current object.
Instead, it creates a new object and then returns it.
So remember here I could have explicitly
had a return statement and I could have said,
set the X field to be the value of the X parameter
because the parameter happens to
have the same name as the field.
Set the Y field to be the value of the Y parameter,
set the Z field to be the value of the Z parameter.
Z and semicolon.
I could have done that,
but you just have an expression
as an implicit return statement.
And if the field name, we've seen this already,
if the field name is the same as the local variable
then you can just use this kind of shortened syntax
and the compiler knows what you mean.
All right, so that's my associated function.
And oh, let's go to the main
because we need to actually call this method.
So demo_associated_functions, and it's here.
Okay, so I use my factory method to create a new instance.
So this is starting to look quite similar
to types we've seen before.
Where you say type name colon, colon, new.
Okay, just to emphasize the point
there is nothing magical about this name here.
What I could have done is I could have renamed that method
gimme_point3d_dude, okay,
that would've been slightly less professional, but doable.
And then I could have invoked the method like that.
Okay? But not as conventional as just saying new.
So let's just revert that and just revert that to be new.
Okay, so anyway, whatever it's called, invoke that method
it returns back a point object, which we now own.
Oh, I've declared it mutable.
So I can move it, move it, move it
and I can reset it like so.
So we just need to run the application now
and bask in the glory.
I hope that it's gonna work.
Cargo run.
Fantastic. So created the point object
using the associated function.
You can have other associated functions as well.
Any function that doesn't need a self parameter
would have the same kind of pattern.
I moved it.
When you call any kinda mutator in my demo
it outputs a log message first,
saying we're about to move the object.
And then we did.
And then when I reset it,
the log method will say,
I'm about to reset it, and it does.
And then we print the string.
So I'm about to reset the point, reset the point
and then that's the value afterwards.
Okay. So that was really quite useful, I think.
So the key point then is associated functions,
basically static functions.
There is no static keyword by the way in Rust
but an associated function,
part of the concept of the structure,
but it doesn't have a self parameter.
Okay? So it operates at the structure level
rather than at the actual instance level.