# Rust++: Object-Oriented Programming for Rust!!
[<img alt="crates.io" src="https://img.shields.io/crates/v/plusplus.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/plusplus)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-plusplus-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/plusplus)
This crate provides a `class!` macro that allows you to write Rust in an object-oriented style.
As an example:
```rust
use plusplus::{class, ClassInConstruction, Downcast, DowncastTo, InConstruction};
// define a class
class!{
// classes can derive traits (though right now only `Clone` is supported)
#[derive(Clone)]
pub class ObjectZero {
string: String;
// a class constructor. Classes returned from Rust++ class constructors must be
// parameterized with the `<InConstruction>` type parameter.
pub fn new() -> ObjectZero<InConstruction> {
// classes are initialized in the constructor with the `init_class!` syntax.
//
// this takes all the class's fields as an argument, plus a `superclass` field for
// initializing the superclass (when present)
init_class! {
string: "hello".into(),
another_field: 26,
}
}
// fields can have normal Rust visibility modifiers and be defined
// anywhere in the class
pub(crate) another_field: i32;
pub fn set_string(&mut self, str: String) {
self.string = str;
}
}
}
// object inheritance works across module and crate boundaries
mod object_one {
use plusplus::{class, InConstruction};
use reqwest::Response;
class!{
// ObjectOne is a subclass of ObjectZero. it inherits all of ObjectZero's methods and
// fields, and can extend ObjectZero's methods with new behavior
//
// A subclass must derive all traits the parent class derives
#[derive(Clone)]
pub class ObjectOne: super::ObjectZero {
string_one: String;
reqwest_url: String;
pub fn new() -> ObjectOne<InConstruction> {
init_class! {
// initialize the superclass
superclass: super::ObjectZero::new(),
string_one: String::new(),
reqwest_url: "https://crouton.net/".into(),
}
}
pub fn print_example(&self) {
println!("hi from ObjectOne");
}
// you can also write async functions!
pub async fn get_url(&self) -> reqwest::Result<Response> {
reqwest::get(&self.reqwest_url).await
}
// you override a superclass's methods by declaring an Override block with the name
// of the class you're overriding
override super::ObjectZero {
pub fn set_string(&mut self, str: String) {
// you prefix method invocations with the `super_` prefix to call the
// parent's implementation of the method. this allows you to extend methods
// with new behaviors!
self.super_set_string(str.clone());
self.string_one = str;
}
}
}
}
// if you want to define methods that can't be overridden, just put them in a normal
// impl block
impl ObjectOne {
pub fn string_one(&self) -> &str {
&self.string_one
}
}
}
use object_one::ObjectOne;
use reqwest::Response;
class!{
#[derive(Clone)]
pub class ObjectTwo: ObjectOne {
string_two: String;
pub fn new(two: impl Into<String>) -> ObjectTwo<InConstruction> {
init_class! {
superclass: object_one::ObjectOne::new(),
string_two: two.into(),
}
}
// in order to override a class's method, you must create an override block
// for the class that declared that method
override ObjectOne {
pub fn print_example(&self) {
self.super_print_example();
println!("hello from ObjectTwo");
}
pub async fn get_url(&self) -> reqwest::Result<Response> {
let response = self.super_get_url().await?;
println!("url get!!!");
Ok(response)
}
}
// you can override methods from any parent class in the class hierarchy
override ObjectZero {
pub fn set_string(&mut self, str: String) {
self.super_set_string(str.clone());
self.string_two = str;
}
}
}
}
fn main() {
// initialize an object. the `finish` method wraps the class in a `ClassBox`
// and allows it to behave polymorphically.
let object_two = ObjectTwo::new("hello!").finish();
// prints:
// ```
// hi from ObjectOne
// hello from ObjectTwo
// ```
object_two.print_example();
println!();
let object_as_one = object_two.upcast(); // is ClassBox<ObjectOne>
// calling a class method from anywhere in the class hierarchy will always result in the
// deepest implementation of that method getting executed. so, this also prints:
// ```
// hi from ObjectOne
// hello from ObjectTwo
// ```
object_as_one.print_example();
println!();
// you can call a method with the `my_` prefix in order to bypass the method overload and
// call that class's own implementation of the method. so this prints:
// ```
// hi from ObjectOne
// ```
object_as_one.my_print_example();
println!();
let mut object_as_base = object_as_one.upcast(); // is ClassBox<ObjectZero>
// this call results in all three `set_string` methods getting invoked
let new_str = "sets all strings!!";
object_as_base.set_string(new_str.to_string());
// you can't directly access a subclass's fields from a superclass. so, the following line
// would not compile:
// println!("{}" object_as_base.string_two);
let object_as_two = object_as_base
.downcast_to::<ObjectOne>().unwrap()
.downcast_to::<ObjectTwo>().unwrap();
// but, you can access a superclass's fields, within the limits set by visibilty rules
assert_eq!(object_as_two.string, new_str);
assert_eq!(object_as_two.string_one(), new_str);
assert_eq!(object_as_two.string_two, new_str);
let tokio_rt = tokio::runtime::Builder::new_current_thread().enable_io().build().unwrap();
tokio_rt.block_on(async {
let object_as_one = object_as_two.upcast();
// method overloads work on async methods, too. this prints:
// ```
// url get!!!
// ```
let response = object_as_one.get_url().await.unwrap();
// prints:
// ```
// <html>
// <title> Crouton
// </title>
// <body bgcolor="white" text="black">
// <img src="crouton.png" alt="Crouton">
// </body>
// </html>
// ```
// delightful....
println!("{}", response.text().await.unwrap());
});
}
```
More details, and how this works, can be found in the documentation.