Attribute Macro class
#[class]Expand description
Registers a native Kanzi class inheriting from a specific Kanzi base class.
§Attributes
-
metaclass(name = "LIT")- optional - Specifies metaclass name for new Kanzi class. If not provided, metaclass name will be{file_name}.{class_name}(e.g. forRustStructintest/test_case.rsname will be"test_case.RustStruct"). -
metaclass(base = BASE_TYPE)- Specifies a base native class the registering class is inheriting from. -
metaclass(property_type = Ident::<PTYPE>)- optional* - Specifies a property type created alongside a new Kanzi class. -
metaclass(message_type = Ident::<MTYPE>)- optional* - Specifies a message type created alongside a new Kanzi class. -
metaclass(editor_info = EditorInfo { .. })- optional - Specifies editor info for a registered class. The syntax is matching the one used for creating properties.
#[kanzi::class]
#[metaclass(name = "...")]
#[metaclass(base = BASE_TYPE)]
#[metaclass(property_type = PROP::<PROP_TY>)]
#[metaclass(property_type = ...)]
#[metaclass(message_type = MESG::<MESG_TY>)]
#[metaclass(message_type = ...)]
#[metaclass(editor_info = EditorInfo { .. })]
pub struct #TYPE { ... }§State
To define functions operating on state inside #TYPE kanzi::state macro is used.
Functions defined for this macro can either borrow state mutably or immutably.
§Requirements
newfunction is required and will be called every time the class instance is created.
#[kanzi::state]
impl #TYPE {
fn new(domain: &kanzi::Domain, name: &kanzi::KanziStr) -> kanzi::Result<Self> { ... }
}§Methods
To define functions operating on an initialized object instance kanzi::methods macro is
provided. Methods get access to this pointer and to a base pointer, providing a way of
calling into Kanzi methods defined in the inheritance tree of a derived class.
§Members
self.this- Gives access to methods defined on a parent class. This can be used, for example, for adding notification handlers on a derivedDataSource, sinceadd_modified_notification_handleris implemented onDataContext, from whichDataSourceinherits.self.base- Gives access to base methods of a derived class. This is useful if you want to get access to a default implementation of an overriden function.self.state- Gives access toRefCellstoring state of a derived class. The state is hidden behindRefCell, because Rust invariants need to be maintained (no aliasing). And since an actual C++ object was defined, it is possible to get multiple references to it from Kanzi runtime, which we cannot statically control. Borrowing state withself.state.borrow()orself.state.borrow_mut()should be as much reduced in scope as possible, since the usage ofRefCellis very prone to panics (for example, if you mutably borrowed state and, while holding it, call another method, which will borrow state immutably, the second call will fail to execute and panic to ensure Rust aliasing rules.- Helper functions
self.state()andself.state_mut()are provided to respectively either borrow state immutably and mutably.
§The difference between state and methods proc-macros.
statefunctions already expect the state to be borrowed (either mutubly or immutably) for them. They do not have access to eitherthisorbasepointers, which helps ensuring thatRefCellborrowing rules will not be violated (sinceselfonly provides access to its state and not to the object environment withthisandbasepointers, calling into which might result in panics when the state is already borrowed).methodsmethods, on the other hand, can only be borrowed immutably and to get access to the internal state requires you to go through aRefCell. While state can be also modified through those methods, users must be very careful and should try to keep scope of borrowed state to the minimum (for exampleself.state.borrow_mut().field = value, compare this to borrowing the state first and holding ontoRefMutin scope).
#[kanzi::methods]
impl #TYPE {
pub fn with_object(&self) {
let value = self.get_new_value();
self.state_mut().field = value;
}
fn get_new_value(&self) -> u64 {
/* gets a new value */
}
}
...
let object = #TYPE::create(&domain, "TestObject")?;
object.with_object();§Inheritance
An important part of inheritance is to be able to access and override methods of a base class.
To override methods of a base class a specific trait must be implemented (see #Overrides).
For virtual methods all implementations are required, while for overridable methods a default
implementation exists which calls a base class.
When overriding a method you can use base() method on self to get access to methods on a base
class, for example:
#[kanzi::overrides]
impl kanzi::IDataSource for RustDataSource {
fn get_data(&self) -> Option<kanzi::Weak<kanzi::DataObject>> {
/* Run user defined logic */
self.with_object();
/* Finalize by calling base method on `DataSource` */
self.base().get_data().unwrap()
}
}§Overrides
I#BASE_TYPEtrait must be implemented to provide overrides for inherited class:
#[kanzi::overrides]
impl I#BASE_TYPE for #TYPE { ... }§Getters and setters
Each property and message type defined on a #TYPE generates a getter and a setter. Also getters and setters for properties and messages of a base class (and its inheritance list) will be available through auto dereference (the same way it works for general Kanzi classes).
§Complete example
#[kanzi::class]
#[metaclass(name = "Kanzi.RustDataSource")]
#[metaclass(base = kanzi::DataSource)]
#[metaclass(property_type = Path::<kanzi::KanziString>)]
#[metaclass(editor_info = EditorInfo { display_name: "Rust Data Source" })]
pub struct RustDataSource {}
kanzi::property!(Path {
data_type: kanzi::KanziString,
name: "Kanzi.RustDataSource.Path",
default_value: "",
flags: kanzi::ChangeFlags::empty(),
inheritable: false,
metadata: EditorInfo {
display_name: "Path",
tooltip: "Path to XML data source definition",
}
});
#[kanzi::state]
impl RustDataSource {
fn new(_domain: &kanzi::Domain, _name: &kanzi::KanziStr) -> kanzi::Result<Self> {
Ok(Self {})
}
}
#[kanzi::overrides]
impl kanzi::IDataSource for RustDataSource {
fn get_data(&self) -> Option<kanzi::Weak<kanzi::DataObject>> {
self.base().get_data().unwrap()
}
}Note that new class needs to be registered in Kanzi before instances of this class can be created.
Meaning that by itself:
let data_source = RustDataSource::create(&domain, "Kanzi.Test.RustDataSource")?; // panics!For that case a specific function named register is generated, after calling which:
RustDataSource::register(&domain)?;
let data_source = RustDataSource::create(&domain, "Kanzi.Test.RustDataSource")?; // works!A complete runnable example can be found at :/tests/test_data_source_director.rs::complete_data_source_example.