Masai Mahapa

30 days of Rust - Day Nineteen - Object Oriented Programming in Rust

30 days of rust - day Eighteen 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 ✌🏼 .

Share