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. for RustStruct in test/test_case.rs name 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
  • new function 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 derived DataSource, since add_modified_notification_handler is implemented on DataContext, from which DataSource inherits.
  • 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 to RefCell storing state of a derived class. The state is hidden behind RefCell, 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 with self.state.borrow() or self.state.borrow_mut() should be as much reduced in scope as possible, since the usage of RefCell is 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() and self.state_mut() are provided to respectively either borrow state immutably and mutably.
§The difference between state and methods proc-macros.
  • state functions already expect the state to be borrowed (either mutubly or immutably) for them. They do not have access to either this or base pointers, which helps ensuring that RefCell borrowing rules will not be violated (since self only provides access to its state and not to the object environment with this and base pointers, calling into which might result in panics when the state is already borrowed).
  • methods methods, on the other hand, can only be borrowed immutably and to get access to the internal state requires you to go through a RefCell. 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 example self.state.borrow_mut().field = value, compare this to borrowing the state first and holding onto RefMut in 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_TYPE trait 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.