30 days of Rust - Day Nineteen - Object Oriented Programming in Rust
As a programmer, chances are you have come across the concept of Object Oriented Programming. Languages such as Java force you into this coding model. It is structured into classes, Objects, methods and attributes.
- Class = A factory for individual objects. (Person)
- Object = An instance of a class. (John who is a Person)
- Methods = functions that are defined inside a class. (john can talk)
- Attributes = the state of an object. (John's age, cell number, etc.)
Day 19 - Object Oriented Programming in Rust.
Rust does not have Objects. Does this then mean one cannot implement some form of Object Oriented Programming in Rust? Well we actually can get pretty close.
There are three common characteristics of Object Oriented languages which are Objects, Encapsulation and Inheritance.
Objects
An object has both data and functions that operate on that data. These functions are often known as methods.
With that in mind, Rust has structs and enums which have impl
blocks allowing them to have methods. Enums and structs are not objects but they provide the same functionality.
struct Person{
name: String,
weight: f32,
height: f32
}
impl Person {
pub fn get_name(self) -> String{
self.name
}
}
Encapsulation
Encapsulation basically refers to the concept of hiding the implementation details from the code calling your object. They only get to interact with your public API. This allows us as programmers to change the internal workings of our code without our end users even realizing.
Let's say we want to create a system to determine your health status. A person is considered either underweight
, normal weight
, overweight
or obese
. How we do it doesn't really matter to the end user of our healthcare API. Currently we use the BMI (Body Mass Index).
impl Person {
pub fn get_name(&self) -> &str{
&self.name
}
fn calculate_bmi(&self) -> f32{
self.weight / self.height.powf(2.0)
}
pub fn get_health_status(&self) -> String{
let bmi = self.calculate_bmi();
println!("bmi = {}", bmi);
if bmi < 18.5{
String::from("underweight")
} else if bmi >= 18.5 && bmi <25.0 {
String::from("Normal Weight")
} else if bmi >= 25.0 && bmi <30.0 {
String::from("Overweight")
} else {
String::from("Obese")
}
}
}
Currently the code above exposes only 2 methods to the outside, namely get_name
and get_health_status
which are marked by the pub
keyword. So tomorrow if we came up with a new way of determining the BMI we can now easily change the implementation of the calculate_bmi
method because its implementation is hidden from the outside.
So far so good. Rust does have OOP characteristics.
Inheritance
Inheritance allows one Class to inherit the Methods and properties of another class. So from our example, we can have a Soccer Player who inherits from the Person
class we defined above. A soccer player is a person and has all the properties and methods of a person, with added functionality like the ability to take free kicks.
Rust provides us with the trait
which can have default methods that we can also override.
trait GoalKeeper {
fn save(&self) -> String {
format!("cannot save")
}
}
impl GoalKeeper for SoccerPlayer {
fn save(&self) -> String {
format!("Goal has been saved")
}
}
The above code has a default method called save
inside a GoalKeeper
trait. It is then somewhat been inherited and overridden by the SoccerPlayer
struct, which now has the ability to save a ball.
Conclusion
There still seems to be a debate as to whether Rust is an Object Oriented Language or not, but it is clear that most OOP aspects can be implemented in Rust.
Thanks for spending some time learning Rust with me. Until next time. Peace ✌🏼 .