Expand description
The crate provides basic tools for writing object-oriented code in Rust. Very basic: no multiply inheritance, no generics, no interfaces (but this can be added in future in a limited form), no incapsulation. All classes in hierarchy should have distinct names even if they are located in different modules.
§How to define a class
First you need to import the parent class. All classes should be derived from some parent
class. The only class not having any parent is Obj
defined in basic_oop::obj
module. It contains
no fields or methods. Lets import it:
import! { pub test_class:
use [obj basic_oop::obj];
}
Here test_class
correspond to our new class name.
(Although it is possible to use different names in the import section and in the class definition,
doing so is not recommended to avoid confusion among users of the class.)
The pub keyword means that our class will be public. In case of a private class, it should be omitted.
All types that we plan to use in the method signatures of our class should also be imported as unique names. For example:
import! { pub test_class:
use [obj basic_oop::obj];
use std::rc::Rc;
use std::rc::Weak as rc_Weak;
}
Now we can start to define our class using class_unsafe
macro.
Classes defined with the class_unsafe
macro are intended for use either with the Rc
smart pointer or with the Arc
smart pointer.
Suppose we don’t need reference counter atomicity. Then our class definition will be the next:
#[class_unsafe(inherits_Obj)]
pub struct TestClass { }
Each class should have two constructors: one for creating this particular class and one for calling it from the constructor of the inheritor:
impl TestClass {
pub fn new() -> Rc<dyn IsTestClass> {
Rc::new(unsafe { Self::new_raw(TEST_CLASS_VTABLE.as_ptr()) })
}
pub unsafe fn new_raw(vtable: Vtable) -> Self {
TestClass { obj: unsafe { Obj::new_raw(vtable) } }
}
}
§Fields
To add a field to class, we just write it in ordinar way:
#[class_unsafe(inherits_Obj)]
pub struct TestClass {
field: RefCell<Rc<String>>,
}
impl TestClass {
pub fn new(field: Rc<String>) -> Rc<dyn IsTestClass> {
Rc::new(unsafe { Self::new_raw(field, TEST_CLASS_VTABLE.as_ptr()) })
}
pub unsafe fn new_raw(field: Rc<String>, vtable: Vtable) -> Self {
TestClass {
obj: unsafe { Obj::new_raw(vtable) },
field: RefCell::new(field),
}
}
}
§Non-virtual methods
To add a method, it is needed to specify a fictive field with #[non_virt]
attribute and
function type:
#[class_unsafe(inherits_Obj)]
pub struct TestClass {
...
#[non_virt]
get_field: fn() -> Rc<String>,
}
Then TestClassExt
extension trait will be generated contained appropriate function calling
TestClass::get_field_impl
. We must provide this implementing function:
impl TestClass {
fn get_field_impl(this: &Rc<dyn IsTestClass>) -> Rc<String> {
this.test_class().field.borrow().clone()
}
}
§Virtual methods
Adding a virtual method is no different from adding a non-virtual method only this time
we use virt
:
#[class_unsafe(inherits_Obj)]
pub struct TestClass {
...
#[virt]
set_field: fn(value: Rc<String>),
}
impl TestClass {
fn set_field_impl(this: &Rc<dyn IsTestClass>, value: Rc<String>) {
*this.test_class().field.borrow_mut() = value;
}
}
§Derived class. Method overriding.
Lets import our class and derive another one from it:
import! { pub derived_class:
use [test_class crate::test_class];
}
#[class_unsafe(inherits_TestClass)]
pub struct DerivedClass {
}
Now we wants to override set_field
, how we do it? Simple:
#[class_unsafe(inherits_TestClass)]
pub struct DerivedClass {
#[over]
set_field: (),
}
impl DerivedClass {
pub fn set_field_impl(this: &Rc<dyn IsTestClass>, value: Rc<String>) {
let value = /* coerce value */;
TestClass::set_field_impl(this, value);
}
}
The type of the overridden function is already known from the base class definition,
so there is no need to re-write it, which is why the type of the phony field is specified as ()
.
§Using the class.
We can create instance of derived class:
let class = DerivedClass::new(Rc::new("initial".to_string()));
We can cast it to base class:
let base_class: Rc<dyn IsTestClass> = dyn_cast_rc(class).unwrap();
We can call both virtual and non-virtual methods:
assert_eq!(base_class.get_field().as_ref(), "initial");
base_class.set_field(Rc::new("changed".to_string()));
assert_eq!(base_class.get_field().as_ref(), "changed coerced");
See full working example in README.md
§Arc-based classes
To get a class based on Arc
instead of Rc
,
use use ObjSync
instead of Obj
:
import! { pub test_class:
use [obj_sync basic_oop::obj_sync];
...
}
#[class_unsafe(inherits_ObjSync)]
pub struct TestClass { }
Modules§
Macros§
- import
- Imports base class into the current scope so that it can be inherited from.
Type Aliases§
- Vtable
- The pointer to the table containing pointers to class virtual functions.
Attribute Macros§
- class_
unsafe - Generates class and appropriate helper types and traits.