Rust is not an object-oriented language in the traditional sense, like Java or C++. It is a multi-paradigm language that takes inspiration from various programming styles, including functional, imperative, and concurrent programming. While it doesn't provide all the typical OOP features such as class inheritance, it offers powerful mechanisms to achieve similar goals like encapsulation, polymorphism, and code reuse through its unique type system and trait system.
1. Encapsulation
Rust achieves encapsulation through `struct`s and `enum`s, combined with `pub` and private visibility rules. Fields within a `struct` are private by default. You can define public methods on `struct`s using `impl` blocks, which allows controlled access and manipulation of the internal state. This promotes information hiding, a core tenet of encapsulation.
2. Polymorphism
Rust provides two main forms of polymorphism:
* Static Polymorphism (Generics): Achieved using generics and trait bounds. Functions or data structures can operate on a variety of types as long as those types implement a specified set of traits. The exact type is resolved at compile time, leading to zero runtime overhead.
* *Example*: A function that takes any type `T` as long as `T` implements the `Display` trait.
* Dynamic Polymorphism (Trait Objects): Achieved using trait objects (`dyn Trait`). This allows you to work with different types that implement a particular trait through a common interface at runtime. Trait objects are typically stored in a `Box<dyn Traant>` (or `&dyn Trait`) which is a pointer to the actual data and a vtable (virtual method table) containing pointers to the methods implemented for that specific type. This incurs a small runtime cost due to dynamic dispatch.
* *Example*: A vector holding various shapes (`Circle`, `Rectangle`), all accessed via a common `Shape` trait, where the specific `draw` method is called based on the concrete type at runtime.
3. Inheritance (or lack thereof)
Rust explicitly *does not* have class inheritance. This design choice helps avoid issues like the "diamond problem" and promotes a design philosophy of "composition over inheritance." Instead of inheriting data and behavior from a base class, Rust encourages you to:
* Use Traits for Shared Behavior: Traits define a set of methods that a type *can* implement. Any type that implements a trait guarantees it provides that specific behavior. This allows for code reuse and defining common interfaces without tying types into a rigid hierarchy.
* Use Struct Composition: Instead of inheriting fields, you can embed other structs within a struct. This means one struct "has a" relationship with another, rather than "is a" relationship. This offers more flexibility and avoids tight coupling.
Summary
Rust provides a robust and type-safe approach to object-oriented programming concepts. It emphasizes clear boundaries, explicit behavior definitions, and compile-time guarantees, largely through its powerful trait system and ownership model, rather than traditional class-based inheritance hierarchies.
Example Code
```rust
// 1. Encapsulation: Using structs and methods
// A 'Book' struct with private fields and public methods
pub struct Book {
title: String,
author: String,
pages: u32,
}
impl Book {
// Constructor method to create a new Book
pub fn new(title: String, author: String, pages: u32) -> Book {
Book {
title,
author,
pages,
}
}
// Public method to get the book's title
pub fn get_title(&self) -> &str {
&self.title
}
// Public method to get the book's author
pub fn get_author(&self) -> &str {
&self.author
}
// Public method to display book information
pub fn display_info(&self) {
println!("Title: {}, Author: {}, Pages: {}", self.title, self.author, self.pages);
}
}
// 2. Polymorphism: Using Traits (dynamic and static)
// Define a 'Shape' trait for common behavior
pub trait Shape {
fn area(&self) -> f64;
fn draw(&self);
}
// Implement 'Shape' for a 'Circle' struct
pub struct Circle {
radius: f64,
}
impl Circle {
pub fn new(radius: f64) -> Circle {
Circle { radius }
}
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn draw(&self) {
println!("Drawing a Circle with radius {} and area {}", self.radius, self.area());
}
}
// Implement 'Shape' for a 'Rectangle' struct
pub struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
pub fn new(width: f64, height: f64) -> Rectangle {
Rectangle { width, height }
}
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn draw(&self) {
println!("Drawing a Rectangle with width {} and height {} and area {}", self.width, self.height, self.area());
}
}
// Function demonstrating dynamic polymorphism with trait objects
fn draw_all_shapes(shapes: &Vec<Box<dyn Shape>>) {
println!("\n--- Drawing all shapes ---");
for shape in shapes {
shape.draw(); // Calls the specific draw method for Circle or Rectangle at runtime
}
}
// Function demonstrating static polymorphism with generics and trait bounds
fn print_area<T: Shape>(shape: &T) {
println!("Calculated area (static): {}", shape.area());
}
fn main() {
println!(" Encapsulation Example ");
let my_book = Book::new(
"The Rust Programming Language".to_string(),
"Steve Klabnik and Carol Nichols".to_string(),
560,
);
my_book.display_info();
// Cannot directly access my_book.title; it's private.
println!("Book title (via getter): {}", my_book.get_title());
println!("\n Polymorphism Example ");
let circle = Circle::new(10.0);
let rectangle = Rectangle::new(5.0, 8.0);
// Dynamic Polymorphism: Store different concrete types in a Vec<Box<dyn Shape>>
let mut shapes: Vec<Box<dyn Shape>> = Vec::new();
shapes.push(Box::new(circle));
shapes.push(Box::new(rectangle));
draw_all_shapes(&shapes);
// Static Polymorphism: using generics
let another_circle = Circle::new(3.0);
let another_rectangle = Rectangle::new(4.0, 6.0);
print_area(&another_circle);
print_area(&another_rectangle);
}
```








Object Oriented Programming Features of Rust