1. What are methods?
In Rust, methods and functions are defined very similarly:
- Use all
fn
Let's declare. - All can have parameters and return values.
- All contain a piece of code logic executed when called.
The difference is:Methods must be defined in a specific type (e.g.struct
、enum
or in the context of a trait object). And the first parameter of the method must be written asself
(can beself
、&self
or&mut self
), used to represent a specific instance of calling the method.
Let's take a look at a simple example. Suppose we have oneRectangle
Structure:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, }
If you want toRectangle
The instance adds a function to calculate area, we canimpl
Define a method for it in the (implementation) blockarea
:
impl Rectangle { fn area(&self) -> u32 { * } }
- here
impl Rectangle { ... }
Indicates that all functions in this block areRectangle
Type association. -
fn area(&self) -> u32
Note: This is a method, the first parameter is&self
,expressIn the form of immutable referencesAccess the currently called methodRectangle
Example. -
and
That is, the field that represents the instance. use
self
Access fields are very intuitive.
existmain
In the function, after we create a rectangle instance, we can use method syntax to get the area:
fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!("rect1 The area is:{}", ()); }
After running, the output will be:
The area of rect1 is: 1500
2. Why use &self instead of &Rectangle?
In ourarea
When refactoring from a normal function into a method, you will notice that the function signature is from
fn area(rectangle: &Rectangle) -> u32 { ... }
Become
fn area(&self) -> u32 { ... }
This is becauseimpl Rectangle
In this context, Rust gives a more readable way: let the first parameter automatically becomeself
,andSelf
It is the type alias corresponding to the current implementation block.
If you needReviseThe field of the instance, you can write the first parameter as&mut self
; if neededObtain ownershipAnd maybe "convert" it into something else inside the method, then useself
. But this kind of transfer of ownership to the method itself is rare.
In most cases, we just want to read the structure data without changing it, then use&self
Most commonly, it also allows the caller to continue using this instance.
3. Methods of the same name field and the same name
If you areRectangle
There is also a field calledwidth
, and also want to define a method calledwidth
, this is legal. for example:
impl Rectangle { fn width(&self) -> bool { > 0 } }
When called:
-
(without brackets) Accessed is a field
width
value of . -
()
(with brackets) calls a method with the same name, returning a boolean value.
In many languages, if you just want to return field values simply, this method will be called "getter".
Rust does not automatically generate getters for you, but you can define them yourself.
In this way, you can just set the field to private, but expose the read-only method to the outside world, allowing it to be accessed safely from the outside.
4. Borrowing and dereference: Why don't you need to write & or * when calling a method?
In C/C++, if you want to call member functions through pointers, you need to write->
. Or, if you have a pointer on hand, explicitly(*object).method()
wait.
There is no need to be such trouble in Rust becauseAutomatic reference and dereferenceLet you write directly()
。
In fact, these calls are the same:
(&p2); (&p1).distance(&p2);
Rust will be signed according to the method (the first parameter is&self
、&mut self
stillself
) to automatically infer whether you need to add it for you&
、&mut
or*
. This greatly simplifies the syntax when calling methods.
5. Methods can have multiple parameters
There is no difference between methods and functions in terms of parameters exceptThe first parameter isself
You can add other parameters freely.
For example,Rectangle
Define another methodcan_hold
, used to check whether the "current rectangle" can fully accommodate another rectangle:
impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { > && > } }
Then use it like this:
fn main() { let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle { width: 10, height: 40 }; let rect3 = Rectangle { width: 60, height: 45 }; println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); // true println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); // false }
6. Associated Functions
Ifimpl
Functions defined in blocksNo self
Parameter, then it is not a method, butAssociated function。
Associative functions are often used to provide functions similar to "constructors".
For example, if you want to quickly construct a "square":
impl Rectangle { // Associative function fn square(size: u32) -> Self { Self { width: size, height: size, } } }
When calling, use::
Syntax to call the associative function:
fn main() { let sq = Rectangle::square(3); println!("square sq: {:#?}", sq); }
The print result is:
Square sq: Rectangle {
width: 3,
height: 3
}
In the standard library, we often see such asString::from("Hello")
. It does not require an existing oneString
Instance can be called directly to create a new string.
7. Multiple impl blocks
You can write multiple for the same typeimpl
Blocks, such as:
impl Rectangle { fn area(&self) -> u32 { * } } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { > && > } }
It's the same as writing them in the same wayimpl
There is no essential difference in it. The reason Rust allows you to write separately is to make it more flexible in organizing code in some cases (such as generics, trait implementations, etc.).
8. Summary
-
method: Must be defined in a certain type (e.g.
struct
)ofimpl
In the block, the first parameter isself
(mutable or immutable). Methods are often used to describe certain behaviors of an instance of that type, to read or write its internal data. -
Associative Functions:exist
impl
Defined in blocks but not includedself
Function of parameters. Commonly used to construct new instances or provide some functionality that is not related to the instance. - Rust ownsAutomatic reference and dereferenceFeatures that allow us to use it concisely
()
To call the method. - Multiple
impl
Blocks can coexist, providing great flexibility to the organization of code.
By defining methods for custom types, we can not only make the code more readable and put related behaviors into the sameimpl
In the block, you can also make full use of ownership, borrowing and other features to ensure memory security and concurrency security.
Hope this article helps you figure out how to write and when to use it in Rust&self
、&mut self
、self
, and how to make the code more concise and elegant with the help of associative functions.
If you are still interested in enums or traits in Rust, you might as well continue reading the following chapters, includingstruct
Likewise, it is also an important tool for building complex logic.
Of course, the above is personal experience. I hope you can give you a reference and I hope you can support me more.