14 Struct Methods
Learn how to implement methods for stucts. Be able to create associated functions and understand the difference between Self
, &self
, and &mut self
Associated Functions
Typically methods are used on instantiations of a variable such as:
let me = Person { name: "Josiah".to_string(), age: 29 };
let birth_year = me.year_of_birth();
Sometimes it makes sense to use an associated function. These are functions that are called from the type itself e.g. Type::function()
. They’re typically used for constructors. For example
let me = Person::new("Josiah".to_string(), 29);
They’re created using an impl
block.
impl
ement methods
impl
is short for implement. We can implement methods for structs using the following syntax:
impl TypeName {
fn method() {
todo!()
}
}
Associated functions are methods that do not reference the type itself. To create a new()
constructor function that returns the type we can set the return type to Self
:
impl Person {
fn new(name: String, age: i32) -> Self {
{ name, age }
Person }
}
This gives us the ability to use Person::new()
Self references
However, it often makes sense to call methods from an instantiation itself.
We can do this by setting the first argument to &self
which provides a reference to the struct the method is called on.
impl Person {
fn greet(&self) {
println!("Hello, my name is {} and I am {} years old.", self.name, self.age);
}
}
Using a &self
reference means you cannot move the inner fields, only borrow them.
We can also have arguments that refer to other instantiations of the same type via &Self
or Self
. For example to compare ages of people:
impl Person {
fn is_older_than(&self, other_person: &Self) -> bool {
self.age > other_person.age
}
}
Mutable self
Often it makes sense to modify the fields of a struct. This is when a mutable reference is handy via &mut self
.
For example we can implement methods to rename()
and celebrate_birthday()
:
impl Person {
fn rename(&mut self, new_name: &str) {
self.name = new_name.to_string();
}
fn celebrate_birthday(&mut self) {
self.age += 1;
}
}
Exercise
- Implement a
Point::new()
associated function- Using arguments
x: f64
andy: f64
- It should return
Self
- Using arguments
- Define a method
euclidean_distance()
which calculates the distance between itself and anotherPoint
- Use arguments
&self
anddestination: &Self
- Return
f64
- Distance is calculated as \(\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}\)
- Use
.powi(2)
to square a value andx.sqrt()
to get the square root
- Use arguments
- In
main.rs
create twoPoint
structs and calculate the distance between them.
View solution
impl Point {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
fn euclidean_distance(&self, destination: &Self) -> f64 {
let x_diff = (self.x - destination.x).powi(2);
let y_diff = (self.y - destination.y).powi(2);
+ y_diff).sqrt()
(x_diff }
}
fn main() {
let a = Point::new(0.0, 5.0);
let b = Point::new(-10.0, 1.5);
let distance = a.euclidean_distance(&b);
println!("Distance between a and b is {distance}");
}
Bonus: Exercise 2
Euclidean distance is useful only on a rectangle. What about on a sphere? Haversine distance is used to calculate the distance on a sphere. The Haversine formula computes the distance between two points on a sphere given their longitudes and latitudes.
It is defined by the following (messy) equation:
\[a = \sin^2\left(\frac{\Delta\phi}{2}\right) + \cos\phi_1 \cdot \cos\phi_2 \cdot \sin^2\left(\frac{\Delta\lambda}{2}\right)\]
\[c = 2 \cdot \text{atan2}(\sqrt{a}, \sqrt{1-a})\]
\[d = R \cdot c\]
Where:
- \(\phi\) represents latitude (in radians) (use
.to_radians()
) - \(\lambda\) represents longitude (in radians) (use
.to_radians()
) - \(R\) is the Earth’s radius (you can use
6_371_008_7714f64
meters as a mean radius) - \(\Delta\phi\) is the difference in latitude between the two points (\(\phi_2 - \phi_1\))
- \(\Delta\lambda\) is the difference in longitude between the two points (\(\lambda_2 - \lambda_1\))
- \(\text{atan2}(y, x)\) is the arctangent of \(y/x\), using the signs of both arguments to determine the correct quadrant.
Hints for Implementation:
- Remember to convert your latitude and longitude values from degrees to radians using the
.to_radians()
method onf64
. - The
c
part of the formula, \(2 \cdot \text{atan2}(\sqrt{a}, \sqrt{1-a})\), can also be implemented using2 * a.sqrt().asin()
in Rust, which is a common simplification whena
is within the valid domain forasin
.
View solution
impl Point {
fn haversine_distance(&self, destination: &Self) -> f64 {
let radius = 6_371_008.7714; // Earth's mean radius in meters
let theta1 = self.y.to_radians(); // Latitude of point 1
let theta2 = destination.y.to_radians(); // Latitude of point 2
let delta_theta = (destination.y - self.y).to_radians(); // Delta Latitude
let delta_lambda = (destination.x - self.x).to_radians(); // Delta Longitude
let a = (delta_theta / 2f64).sin().powi(2)
+ theta1.cos() * theta2.cos() * (delta_lambda / 2f64).sin().powi(2);
2f64 * a.sqrt().asin() * radius
}
}