Skip to content

Rust Programming: Balancing OOP and FP Paradigms

Overview

Summary:
A concise guide on blending Object-Oriented Programming (OOP) and Functional Programming (FP) paradigms in Rust, with fintech-inspired examples for static and dynamic dispatch.


Key Takeaways

  • Rust Philosophy: Combines safety, performance, and expressiveness.
  • OOP in Rust: Achieved using struct for encapsulation and traits for polymorphism.
  • FP in Rust: Emphasizes immutability, closures, and iterator-based pipelines.
  • Best Practices: Blend OOP and FP paradigms based on the scenario.

Detailed Notes

1. Core Philosophy of Rust

  • Memory Safety Without Garbage Collection: Achieved through ownership, borrowing, and lifetimes.
  • Zero-Cost Abstractions: High-level features with no runtime overhead.
  • Fearless Concurrency: Tools like Send, Sync, Arc, and Mutex enable thread-safe designs.

2. Object-Oriented Programming in Rust

While Rust lacks traditional inheritance, it supports OOP principles through:

  • Encapsulation: Use struct for managing state.
  • Polymorphism: Use traits for dynamic and static dispatch.

Example: Dynamic Dispatch for Payment Processors

trait PaymentProcessor {
    fn process_payment(&self, amount: f64);
}

struct CreditCardProcessor;
impl PaymentProcessor for CreditCardProcessor {
    fn process_payment(&self, amount: f64) {
        println!("Processing credit card payment of ${:.2}", amount);
    }
}

struct PaypalProcessor;
impl PaymentProcessor for PaypalProcessor {
    fn process_payment(&self, amount: f64) {
        println!("Processing PayPal payment of ${:.2}", amount);
    }
}

fn main() {
    let cc_processor = CreditCardProcessor;
    let pp_processor = PaypalProcessor;

    // Dynamic dispatch
    let processors: Vec<&dyn PaymentProcessor> = vec![&cc_processor, &pp_processor];
    for processor in processors {
        processor.process_payment(100.0);
    }
}
Static Dispatch (impl) Dynamic Dispatch (dyn)
Compile-time resolution Runtime resolution
Faster with inlining Slightly slower (vtable lookup)
Less flexible Supports runtime polymorphism

3. Functional Programming in Rust

Rust’s FP tools include:

  • Immutability: Default immutability promotes safety.
  • Closures: Enable functional paradigms.
  • Iterators: Chainable transformations for concise and expressive code.

Example: Functional Pipelines for Transaction Fees

fn main() {
    let transactions = vec![150.0, 75.0, 200.0];
    let fees: Vec<f64> = transactions
        .iter()
        .filter(|&&amount| amount > 100.0)
        .map(|&amount| amount * 0.02)
        .collect();
    println!("Fees: {:?}", fees);
}

4. Best of Both Worlds

Rust’s design supports combining OOP and FP:

Example: Mixed Paradigm for Payment Calculations

trait PaymentMethod {
    fn calculate_fee(&self, amount: f64) -> f64;
}

struct CreditCard;
impl PaymentMethod for CreditCard {
    fn calculate_fee(&self, amount: f64) -> f64 {
        amount * 0.03
    }
}

struct BankTransfer;
impl PaymentMethod for BankTransfer {
    fn calculate_fee(&self, amount: f64) -> f64 {
        5.0
    }
}

fn main() {
    let payments: Vec<(&dyn PaymentMethod, f64)> = vec![
        (&CreditCard, 200.0),
        (&BankTransfer, 300.0),
    ];

    let total_fees: f64 = payments
        .iter()
        .map(|(method, amount)| method.calculate_fee(*amount))
        .sum();

    println!("Total Fees: ${:.2}", total_fees);
}

My Reflections

What I Learned: - Rust provides flexible paradigms for safe, performant coding. - OOP principles like encapsulation and polymorphism can be achieved with traits and structs. - Functional paradigms like immutability and closures align well with Rust’s safety guarantees.

Questions I Still Have: - How can dynamic dispatch be optimized for performance? - Are there scenarios where FP is not ideal in Rust?


References