# Object-Oriented Thinking in Rust

**Object-oriented programming**, also called OOP, is a programming style that is dependent on the concept of *objects*. It is very popular and established. It is like designing and organizing your code by thinking of parts of your program as real-world objects. These objects bring together data (attributes) and functions (behaviors or methods) into one neat package that can communicate with each other. Every object has its own unique set of properties.

The building blocks of OOPs are attributes, methods, classes and object. The principles of OOPs are encapsulation, abstraction, inheritance and polymorphism. We will discuss these blocks and principles.

Rust isn't "object-oriented" in the usual inheritance-based way, but it offers tools—**structs**, **impl** blocks, and **traits**—that let you use many of the same design patterns: encapsulation, abstraction, and polymorphism.

In fact, Rust's approach is very close to object oriented way of thinking, can help you create more maintainable and strong systems.

# Encapsulation

Encapsulation is a core principle of object-oriented programming (OOP). It refers to bundling data (fields/attributes) and behavior (methods) together. In Rust, this is done by defining structs (similar to classes in other programming languages). Beyond just packaging data, encapsulation also involves hiding an object's internal state and implementation details, while providing a clear and accessible way to interact with it. In many traditional OOP languages like Java, C#, and C++, encapsulation is achieved using private fields (attributes) and public methods (getters, setters, business logic) to control access to these fields.

## Structs

In classical OO languages:

* A **class** is a blueprint that defines the data (fields or attributes) and behaviors (methods).
    
* An **object** is an instance of a class.
    

In Rust, `struct`s can be used to group related data (similar to fields in a class). Along with `impl` blocks, which define associated functions and methods, `struct + impl` can serve a similar role to classes.

**Attributes** are variables that represent the state of the object. In the below example future contract attributes represent its state. In other words attributes (or fields) refer to the data contained within an object.

### Example: A Basic Future Contract Struct

A futures contract has attributes such as:

* The **underlying asset** (e.g., a commodity, stock index)
    
* The **quantity** or number of units
    
* The **maturity date**
    
* The **entry price** or the price at which the contract was agreed
    

Here’s an example of how we can represent these attributes in Rust:

```rust
#[derive(Debug)]
struct FutureContract {
    underlying: String,
    quantity: u64,
    maturity_date: String,
    entry_price: f64,
}
```

This `FutureContract` struct holds all the relevant data for our futures contract. It’s analogous to defining a class with four attributes in an OO language.

We can create **instances** of a `struct` (akin to objects) by calling the struct’s name with the field values.

```rust
fn main() {
    let future = FutureContract {
        underlying: String::from("Crude Oil"),
        quantity: 100,
        maturity_date: String::from("2025-12-31"),
        entry_price: 60.50,
    };

    println!("Created a future: {:?}", future);
}

Created a future: FutureContract { underlying: "Crude Oil", quantity: 100, maturity_date: "2025-12-31", entry_price: 60.5 }
```

Here, `future` is an **instance** of `FutureContract`, just as an object would be an instance of a class in a classical OO paradigm.

Rust ensures that objects (instances of structs) are in a valid state after they are created. In an object-oriented way of thinking, an object must have a state; it cannot exist without a valid state. For example, if you create a point with x-axis and y-axis for a 2D coordinate system, a point object must have valid coordinates. You cannot create a point where you know the x-coordinate but the y-coordinate is unknown. Rust provides this by default. In C# you can achieve this using the required modifier.

```rust
#[derive(Debug)]
struct point{
    x:i64 ,
    y: i64
}
fn main() {
    let a = point{x:1};
    println!("Created a point: {:?}", a);
}
```

```rust
error[E0063]: missing field `y` in initializer of `point`
 --> src/main.rs:8:13
  |
8 |     let a = point{x:1};
  |             ^^^^^ missing `y`
```

If we try to create a point without y coordinate we get compile time error.

## Methods and Associated Functions

In many OO languages, methods are defined within the class. Rust does not store methods within structs but uses `impl` blocks (implementation blocks) to define methods associated with a given struct.

Let’s add a **constructor**\-like function (commonly named `new` in Rust). Rust does not have constructors in the classical sense. Instead, Rust uses **associated functions** (often called “factory” or “constructor-like” functions, typically named `new`) within an `impl` block to initialize structs.

Another method to **calculate the contract’s notional value** (quantity times entry price) and a method to **settle** the contract.

```rust
impl FutureContract {
    /// Associated function (like a constructor)
    fn new(underlying: &str, quantity: u64, maturity_date: &str, entry_price: f64) -> Self {
        FutureContract {
            underlying: String::from(underlying),
            quantity,
            maturity_date: String::from(maturity_date),
            entry_price,
        }
    } 
    /// Calculate the notional value of the future
    fn calculate_notional(&self) -> f64 {
        self.quantity as f64 * self.entry_price
    }
    /// Some method representing contract settlement
    fn settle(&self, settlement_price: f64) -> f64 {
        // Gain or loss = (Settlement price - Entry price) * Quantity
        let pnl = (settlement_price - self.entry_price) * self.quantity as f64;
        pnl
    }
}
```

To define the function within the context of `FutureContract`, we start an `impl` (implementation) block for `FutureContract`. Everything within this `impl` block will be associated with the `FutureContract` type.

All functions defined within an `impl` block are called *associated functions* because they’re associated with the type named after the `impl`. The associated functions that take self as parameters are called **methods** because they need instance of the struct. Associated functions that aren’t methods (don’t need self in parameters) are often used for constructors that will return a new instance of the struct. These are often called `new`, but `new` isn’t a special name and isn’t built into the language. The new function in our struct doesn’t take self and act as a constructor.

The `Self` keywords in the return type and in the body of the function are aliases for the type that appears after the `impl` keyword, which in this case is `FutureContract`. To call this associated function, we use the `::` syntax with the struct name.

```rust
fn main() {
    let oil_future = FutureContract::new("Crude Oil", 100, "2025-12-31", 60.50);
    let notional = oil_future.calculate_notional();
    println!("Notional value: ${:.2}", notional);
    let pnl = oil_future.settle(65.00);
    if pnl > 0.0 {
        println!("Profit: ${:.2}", pnl);
    } else {
        println!("Loss: ${:.2}", pnl.abs());
    }
}
```

### `Self`, `self` and `&self` in Methods

`Self` (capital “S”) refers to the type **within** an `impl` block. For instance, in our `impl FutureContract { ... }` block, `Self` is an alias for `FutureContract`. Typically used in **associated functions** (which do not take a `self` parameter) to construct or return an instance of the same type, for example, the `new` function in `impl FutureContract { ... }`. Here, `Self` is simply `FutureContract`. If you rename the struct, you won’t need to update the type references in the methods—`Self` will remain valid as it automatically refers to the type in the current `impl`.

Additionally, you can also see `Self` used as a return type in methods that consume `self` and return another struct of the same type, or partial transformations:

```rust
impl FutureContract {
    fn into_option(self) -> Option<Self> {
        // Example of returning an Option of Self.
        Some(self)
    }
}
```

Here, `Option<Self>` could also be written as `Option<FutureContract>`.

Rust requires us to explicitly declare how we want to receive the instance: (`self`), by immutable reference (`&self`), or by mutable reference (`&mut self`).

### `&self` — Immutable Reference

When a method signature takes `&self`, it borrows the struct **immutably**. This means the method can *read* the fields of the struct but cannot modify them. `calculate_notional` borrows `self` immutably, we can call it multiple times on the same instance, even simultaneously, as long as we don’t try to have mutable reference.

### `self` — Ownership Transfer

A method that takes `self` literally **consumes** the struct instance. After calling this method, the caller loses ownership unless something is returned.

### `&mut self` — Mutable Reference

A method with `&mut self` borrows the instance **mutably**, allowing the method to modify the struct’s fields.

```rust
impl FutureContract {
    // A method that mutates the struct
    fn update_price(&mut self, new_price: f64) {
        self.entry_price = new_price;
    }
}
```

Only one mutable reference to a given object can exist at a time, enforcing Rust’s **borrow-checker** rules.

The self is similar to self in Python, in both Python and Rust, “self” is the way you reference the current instance of a type. Both are explicit in the sense that they appear in the method signature. Rust requires you to be explicit about the **ownership and borrowing** (`self`, `&self`, `&mut self`). In Python, you can read and write to attributes through `self` without any explicit ownership or mutability rules. Rust enforces these constraints via the type system.

### Associated Functions vs. Static Methods in Other Languages

In many object-oriented languages (e.g., Java, C#), you can mark a method as `static`, meaning it does not need an instance of the class to be called. This approach goes against the principles of OOP and can lead to less maintainable code. This might be why there is no specific `static` keyword for methods in Rust.

As we discussed earlier, associated functions that do not have a `self` parameter are called directly from the type, like this: `TypeName::function_name()`, instead of from an instance of the type. These associated functions are the closest equivalent to a "static method."

In JAVA/C#, you write `public static void foo() { ... }`, and call [`MyClass.foo`](http://MyClass.foo)`()`. In C++, You might have `static void foo();` inside a class, and call [`MyClass ::`](http://MyClass.foo)`foo()`;. Python uses `@staticmethod` decorator. In Rust, again, an associated function with no `self` is your “static method.”

You write:

```rust
impl MyStruct {
    fn foo() {
        // ...
    }
}
```

and call `MyStruct::foo()`**.**

### No static field inside a struct

Using the `static` keyword on a class field means that all instances share that field, which isn't really in line with OOP principles and can lead to issues. Many object-oriented languages have moved away from OOP principles to include this feature. In **Java/C#**, the `static` keyword is used on a class field and is usually accessed with `ClassName.staticField`. C++ follows the same idea. Python doesn't have a static keyword but uses class attributes that all instances share. Luckily, Rust doesn't directly support a "static field inside a struct." It avoids this behavior and sticks more closely to OOP principles in this respect.

## pub keywords

Encapsulation is also a practice of restricting direct access to some of the object's components. In Rust, this is done using `pub` (public) and private visibility modifiers.

## Overloading

When you have two or more methods with the same name but different parameters, that's called overloading. **Rust does not support function or method overloading** in the same way that languages like C++, Java, or C# do. In those languages, you can define multiple functions with the *same name* but different *parameter lists* or *types* (e.g., `foo(int x)` vs. `foo(double x)` vs. `foo(int x, int y)`).

Rust, however, **disallows** having two or more functions in the same scope.

```rust
#[derive(Debug)]
struct point{
    x:i64 ,
    y:i64
}
impl point{
    fn my_point(){
        println!("my_point function with no parameter");
    }
    fn my_point(_x:i64){
        println!("my_point function with int parameter");
    }
}
fn main() {
    let a = point{x:1,y:2};
    println!("Created a point: {:?}", a);
    point::my_point();
}
```

Rust **does not** allow these two `my_point` methods to coexist, even though they have different signatures, because it doesn’t support method overloading by parameter signatures.

```rust
error[E0592]: duplicate definitions with name `my_point`
  --> src/main.rs:10:5
   |
7  |     fn my_point(){
   |     ------------- other definition for `my_point`
...
10 |     fn my_point(_x:i64){
   |     ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `my_point`
```

Some would argue that overloadimg makes code base more readable and manageable, but Rust don’t believe in this. Overloading can obscure which function is actually getting called, especially if conversions are implicit. Rust reduce confusion and make sure you have cleaner code. You can use some pattern to achieve this.

# Polymorphism

Polymorphism is a big idea in programming languages. It basically means "many forms." Depending on the situation, your functions can take on different forms and behave differently. For example, function overloading is one kind of polymorphism. Generics are another type, where a function or struct is written in a way that it can work with different types while still keeping everything type-safe, like this: `fn do_something<T: SomeTrait>(x: T) { ... }`.

In the context of object-oriented concepts, we're particularly interested in **subtype polymorphism** (often called *inheritance-based polymorphism* or *inclusion polymorphism*), where an object (or reference) of a **subtype** can be used anywhere its **supertype** is expected.

In classical OOP languages (e.g., Java, C#), subtype polymorphism typically relies on inheritance: a derived class “is a kind of” the base class and can be used in any context expecting the base. Rust does **not** support inheritance in that traditional sense, but it provides a powerful mechanism—**trait objects**—which enable **runtime polymorphism** (often called “dynamic dispatch”).

Rust uses **traits** to define shared behaviors or capabilities. In Rust, when we talk about subtype polymorphism, it means that if different concrete types implement the same trait, you can keep them in the same data structure or pass them to the same function by just referring to that **trait**.

## Traits

Traits are fundamental feature in Rust that provide a way for shared behavior, abstraction and polymorphism. You can think of traits as interfaces in other programming languages. This is how The Rust Programming Language (“the Rust book”) defines 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 can be any type that has certain behavior.***

This is specially important as Rust is not an object-oriented language, in a traditional sense. Struct doesn't support inheritance.

### **Define a Trait**

Let's say we have a variety of financial instruments from different asset classes like equity, equity options, and interest rate derivatives. We need a few generic methods on all the contracts so that we can analyze the portfolio of trades. We define a trait named Contract with one method named npv. The may look like a virtual method in C++.

```rust
    pub trait Contract {
        fn npv(&self) -> f64;
    }
```

In this example, Contract is a trait that requires an implementing type to define the npv method. The npv method takes a reference to self and returns a f64 value.

### **Implement a Trait**

Any type that implements the Contract trait must provide an implementation of the `npv` method. Here is an example of a struct named `EquityOption` that implements the `Contract` trait. `impl` keyword is used to implement traits for a struct. I believe the `impl` keyword is inspired by the `implements` keyword in Java.

```rust
    pub struct EquityOption {
        pub strike: f64,
        pub expiry: f64,
        pub is_call: bool,
    }
    impl Contract for EquityOption {
        fn npv(&self) -> f64 {
            println!("Calculating npv for EquityOption");
            0.0
        }
    }
```

In this example, EquityOption is a struct that implements the Contract trait. If you're familiar with C++, it's like `class EquityOption: public Contract`. Now, let's create another struct called InterestRateSwap that also implements the Contract trait.

```rust
    pub struct InterestRateSwap {
        pub notional: f64,
        pub fixed_rate: f64,
        pub floating_rate: f64,
    }
    impl Contract for InterestRateSwap {
        fn npv(&self) -> f64 {
            println!("Calculating npv for InterestRateSwap");
            0.0
        }
    }
```

We have totally different asset classes, EquityOption and InterestRateSwap, but they both implement the Contract trait. We can create a portfolio of equity options and interest rate swaps and calculate the net present value of the portfolio.

```rust
    fn calculate_portfolio_npv(contracts: Vec<&dyn Contract>) -> f64 {
        let mut npv = 0.0;
        for contract in contracts {
            npv += contract.npv();
        }
        npv
    }
```

Here, `dyn` Contract is a trait object. This means we can pass a vector of any type that implements the Contract trait. This is an example of **dynamic dispatch**. We'll talk more about this when we dive into `dyn` in detail. Now, let's take a look at another implementation of the same function using static dispatch. Traits can also be used to set constraints on generic types, known as **trait bounds**. Here's an example of a generic function that calculates the net present value of a portfolio of contracts.

```rust
    fn calculate_portfolio_npv<T: Contract>(contracts: Vec<T>) -> f64 {
        let mut npv = 0.0;
        for contract in contracts {
            npv += contract.npv();
        }
        npv
    }
```

In this example, T: Contract is a trait bound that specifies that T must implement the Contract trait. This is called static dispatch. The compiler generates a separate version of `calculate_portfolio_npv` for each type T used.

### **Static vs Dynamic Dispatch**

Rust support both static and dynamic dispatch.

* Static dispatch is resolved at compile time. The compiler knows the exact type at compile time and method calls are resolved during compilation.
    
* Dynamic dispatch is resolved at runtime. The compiler doesn't know the exact type at compile time and method calls are resolved via virtual table (vtable), similar to virtual function in C++.
    

Rust's traits are closely tied to its ownership and borrowing model, and it clearly distinguishes between static and dynamic dispatch.

# Inheritance

Actually no inheritance, no `super` or “abstract class” or no deep class hierarchies. You can’t call a parent class method from a child; Rust’s approach is about *interfaces* and *implementations*. Inheritance in rust is only for traits. You can define a trait that inherits from another trait. Here is an example of a trait named Option that inherits from the Contract trait.

```rust
pub trait Option: Contract {
        fn delta(&self) -> f64;
    }
```

In this example, Option is a trait that inherits from the Contract trait and requires an implementing type to define the delta method. If we have a struct named EquityOption that implements the Option trait, it must provide implementations of both the npv and delta methods by implementing respective traits.

```rust
    pub struct EquityOption {
        pub strike: f64,
        pub expiry: f64,
        pub is_call: bool,
    }

    impl Contract for EquityOption {
        fn npv(&self) -> f64 {
            println!("Calculating npv for EquityOption");
            0.0
        }
    }

    impl Option for EquityOption {
        fn delta(&self) -> f64 {
            println!("Calculating delta for EquityOption");
            0.0
        }
    }
```

If you implement the Option traits then you must implement the Contract trait as well, otherwise you have error like this:

```rust
    error[E0277]: the trait bound `EquityOption: Contract` is not unfulfilled
```

This is another example of trait bounds in Rust. It forces the implementing type to implement all the required traits.

## **Default Implementations of Traits**

You can provide default implementations for trait methods.

```rust
    pub trait Contract {
        fn npv(&self) -> f64 {
            println!("Calculating npv for Contract");
            0.0
        }
    }
```

In this example, Contract is a trait that provides a default implementation for the npv method. In this case, you don't have to provide the implementation of the npv method in the implementing type.

```rust
impl Contract for EquityOption {}
```

## **Multiple Trait Implementations**

You can have only one implementation for each type.

```rust
    pub trait Contract {
        fn npv(&self) -> f64;
        fn expiry(&self) -> f64;
    }
    impl Contract for EquityOption {
        fn npv(&self) -> f64 {
            println!("Calculating npv for EquityOption");
            0.0
        }
    }

    impl Contract for EquityOption {
        fn expiry(&self) -> f64 {
            println!("Calculating expiry for EquityOption in days");
            0.0
        }
    }
```

This will give you an error like this:

```rust
    error[E0119]: conflicting implementations of trait `Contract` for type `EquityOption`
```

This prevents the ambiguity in the code. You can have multiple traits but only one implementation for each type.

# Abstraction

Abstraction is a key idea in Object-Oriented Programming (OOP) that lets you hide complex implementation details and show only the essential features or functions to the user. In Rust, you achieve abstraction using traits, modules, and encapsulation.

Traits define a set of methods that a type must implement, allowing you to **abstract over behavior** without specifying the implementation.

### **Modules (Organizing and Abstracting Code)**

Rust’s module system allows you to organize code into logical units and control visibility. By default, items in a module are private, and you can expose only what’s necessary using `pub`.
