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 andtraits
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
, andMutex
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?