Rust Struct Methods
In the Rust programming language, struct methods are user-defined functions that operate on structs.
Methods are functions that operate on an object. When we use the term ‘struct methods’, we are referring to methods that operate specifically on structs. Methods aren’t limited to operating on structs; they can also me used with enum or trait objects as well.
It’s important to recognize that a method is a type of function; a struct method is essentially a function that can be called to act upon a struct instance.
Like other functions, struct methods are declared using the fn keyword:
fn method_name(&self) {}
In this tutorial, we will cover struct methods in Rust. We’ll learn what struct methods are, how to declare them, and how to call struct methods without getting errors from the Rust compiler.
Struct methods are similar to other user-defined functions, but there are a few unique features:
- Methods are declared within the struct context.
- Methods live inside an implementation (impl) block.
- The first parameter of a method is always &self.
Struct Method Implementation Block
All struct methods live inside of an implementation (impl) block. The implementation block references the name of the struct:
impl <struct name> {}
The struct name here is the name of the struct itself, not an instance of the struct (more on this below).
In the following example, we explore using an impl block to declare three methods that all operate on the Student struct:
// Declaring a struct called Student:
struct Student {
name: String,
age: i32,
average: i32,
}
// Using an impl block to declare struct methods:
impl Student {
fn method_1 (&self) {}
fn method_2 (&self) {}
fn method_3 (&self) {}
}
We use the syntax ‘impl <struct name>’ so that the compiler knows which struct to apply the methods inside the impl block to.
A single impl block can hold multiple struct methods.
This example is trivial for several reasons. The methods don’t actually do anything, the struct is not instantiated (i.e. there are no instances of the struct – no Students in this example), and the methods aren’t called.
We’ll build up to a working example below.
Declaring a Struct Method
Struct methods are declared inside the curly braces that define the impl block.
To use a struct method, we will need to learn about the &self parameter and .self notation.
The &self Parameter
As we’ve already seen, the syntax for declaring a struct method differs from other user-defined functions in that the first parameter must be &self. The method can work with other parameters as well, but they must come after the &self parameter in the declaration.
self. Notation
Recall that a struct consists of key-value pairs:
struct MyStruct{
key1: <type 1>,
key2: <type 2>,
key3: <type 3>,
}
Within the struct, each key is associated with a data type. Later when an instance of the struct is declared, each key in the instance is associated with a value (of the declared type).
Inside a method body, we can access the struct’s key-value pairs using self. notation. This associates a key from the struct with the referenced struct instance (using ‘self.’):
self.<key name>
Let’s see how this is done using our Student example:
struct Student {
name: String,
age: i32,
average: i32,
}
impl Student {
fn print_details (&self) {
println!("Student Name: {}", self.name);
println!("Student Age: {}", self.age);
println!("Student Average: {}", self.average);
}
}
Now we have a method that does something useful, but this still wouldn’t compile because we aren’t calling it on an instance of the Student struct.
Calling a Struct Method
To call a struct method, we use dot notation with the name of a struct instance. This tells the method to run on that instance:
<struct instance>.<method>
We can see that calling a method requires two extra things:
- A struct instance must be declared. Calling a method on a struct itself is meaningless, because the struct only provides the framework for a struct instance.
- The struct method must be called by the main() function or a nested function that ultimately gets called from main(). Since control flow follows the main() function, the method must be called within main() or within another function that gets called by main(). The call to the method can be nested within an arbitrary number of functions but ultimately the call to the first function must come from main().
struct Student {
name: String,
age: i32,
average: i32,
}
impl Student {
fn print_details (&self) {
println!("Student Name: {}", self.name);
println!("Student Age: {}", self.age);
println!("Student Average: {}", self.average);
}
}
fn main() {
let bill = Student {
name: "Bill".to_string(),
age: 42,
average: 86
};
// Calling the print_details() method:
bill.print_details();
}
Standard Output:
Student Name: Bill
Student Age: 42
Student Average: 86
In this example, we created an instance of the Student struct called ‘bill’. The print_details() method is then called using dot notation ‘bill.print_details()’.