Object Oriented Programming Features of Rust1
Note: There is no program included in this chapter, as it's notes only.
The Gang of Four defines OOP this way:
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 object-oriented: structs and enums have data,
and impl
blocks provide methods on structs and enums. Even though Rust doesn't
call structs or enums objects, they provide the same functionality.
Another aspect associated with OOP is encauspulation; Rust provides pub
:
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
Yet another aspect is inheritance, gaining the parent object's data and behavior without defining them again. With this definition, Rust is not object-oriented. However, Rust has other tools where you might pick inheritance:
- Traits support default implementations.
- Traits support polymorphism, i.e. common behavior.
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
When we use trait objects, Rust must use dynamic dispatch; which incurs a runtime cost over static dispatch and disables inlining. However, we get extra flexibility - so it's a tradeoff worth considering.
A more Rust-y State
pattern is using types to encode states and behavior:
pub struct Post {
content: String,
}
pub struct DraftPost {
content: String,
}
impl Post {
pub fn new() -> DraftPost {
DraftPost {
content: String::new(),
}
}
pub fn content(&self) -> &str {
&self.content
}
}
impl DraftPost {
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
Note that Post::new
creates a DraftPost
, and only Post
has a public
content
method. It's now impossible to get around these (contract) constraints
without a compiler error.