Object Oriented Programming (OOP) in Rust

Internship at OpenGenus

Get FREE domain for 1st year and build your brand new site

In this article, we have explored Object Oriented Programming (OOP) in Rust and covered topics like Object, Encapsulation, Inheritance, Polymorphism and others along with Rust code examples.

Table of contents:

  1. Introduction to OOP
  2. What is Rust?
  3. Objects and Encapsulation
  4. Inheritance and Polymorphism

Let us get started with Object Oriented Programming (OOP) in Rust.

Introduction to OOP

Object Oriented Programming (OOP) is a term that exists since 1967, coined by Alan Key. In OOP Architecture, the code is structured in Objects, that communicate with each other through public 'interfaces' but do not know how each other work internally.

That means a programmer can avoid worrying about how the objects he's interacting with are implemented internally. And even if that implementation changes or is unknown, as long as the public functions he's using do not change, his code will still work. (Unless of course their implementation is wrong.)

What is Rust?

But before going further, what is Rust?

The Rust programming language helps you write faster, more reliable software. High-level ergonomics and low-level control are often at odds in programming language design; Rust challenges that conflict. Through balancing powerful technical capacity and a great developer experience, Rust gives you the option to control low-level details (such as memory usage) without all the hassle traditionally associated with such control.

That being said, there are many different definitions for OOP, and depending on which one you consider true, Rust may or may not be an OOP language. Many OOP languages share certain characteristics, such as Objects, Inheritance, Encapsulation..

Let's explore how Rust deals with these things! (Examples will be taken from the Rust book and linked to directly aswell for easy referencing)

Objects and Encapsulation

In the book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley Professional, 1994), objects are defined as:

Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data. The procedures are typically called methods or operations.

Using this definition, Rust is an OOP language. Rust has structs and impl blocks that can be translated as a package of data and ways to operate on that data respectively.

pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}

impl AveragedCollection {
    pub fn add(&mut self, value: i32) {
        self.list.push(value);
        self.update_average();
    }

    pub fn remove(&mut self) -> Option<i32> {
        let result = self.list.pop();
        match result {
            Some(value) => {
                self.update_average();
                Some(value)
            }
            None => None,
        }
    }

    pub fn average(&self) -> f64 {
        self.average
    }

    fn update_average(&mut self) {
        let total: i32 = self.list.iter().sum();
        self.average = total as f64 / self.list.len() as f64;
    }
}

As we can see in the above code snippet, we have a struct, and below that, an impl block. The struct is comparable to a class in C# or any similar languages. The Impl block, is basically the functions that you would put inside that class.

The struct is marked pub, for public, meaning other code can use it, but the fields inside are private. The data is encapsulated within the struct 'object'. The functions add, remove and average are also marked as pub, thus exposing this 'interface' to the outside world, while update_average remains private, meaning only the AveragedCollection structure can use it. Which makes sense. Only the collection should be capable of updating it's own data. This eliminates the problem of unwanted tampering of data by outside sources, so you know if the data is wrong, the problem is in there. (Which is also one of the key aspects of SOLID programming, but it's outside the scope of this article)

Inheritance and Polymorphism

Inheritance is a mechanism whereby an object can inherit from another object’s definition, thus gaining the parent object’s data and behavior without you having to define them again.

Considering inheritance, the Rust programming language has no way of specifying that a struct inherits attributes or functionality from another one. So in this sense, Rust is not an object oriented programming language. However there are other solutions that the Rust language offers that are similar to (but not quite) inheritance, for example, Traits.

A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior.

Note: Traits are similar to a feature often called interfaces in other languages, although with some differences.

pub trait Summary {
    fn summarize(&self) -> String;
}

This is an example of a Trait. Just like an interface, any class or structure in the case of Rust that uses this, MUST implement it somewhere in it's own definition. A trait can have multiple methods in it's body, and anyone using it must implement them all.

The implementation would look something like this..

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

Although sometimes it's useful or necessary to have some default implementation for these. Which is possible.

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

If we want to use the default implementation, we would have an empty impl block.

To many people, polymorphism is synonymous with inheritance. But it’s actually a more general concept that refers to code that can work with data of multiple types. For inheritance, those types are generally subclasses.

Rust instead uses generics to abstract over different possible types and trait bounds to impose constraints on what those types must provide. This is sometimes called bounded parametric polymorphism.

To be able to explain thoroughly and well what these are, I'd have to first explain a few other topics more specific about Rust's syntax and way of doing things, so for now, know that it's possible to have some form of polymorphism in Rust, just not the way classical OOP languages do it.

Using structs, traits and impl blocks, you can code in an OOP design style, but as you can see, it's difficult to say for sure, definitively, if Rust is or is not an OOP language.

In my opinion I'd say it's a versatile language, that in a pinch can do it. If you wish to learn OOP in particular, I'd recommend going for one of the more traditional OOP languages like C#, Java or even C++.

That's it for this article at OpenGenus. In the future we can explore Rust more in-depth!

Thank you for reading.