1 unstable release
0.1.0 | Jan 7, 2023 |
---|
#1881 in Rust patterns
17KB
193 lines
Dynamic struct
A derive macro for creating push-based reactive properties for structs (with named fields only).
Why push-based?
Lazy poll-based reactive systems typically require wrapping the values and adding RefCells or flags to cache and update values. Event-based system require a subscription model.
The plumbing for adding push-based change propagation is done via macros at compile-time and the generated code can be inlined during compilation, becoming a zero-cost abstraction at run-time (same as re-calculating the dynamic properties by hand when their dependencies change)
The types can also be left untouched, no need for wrapping and dereferencing.
How to use
1) Add as a dependency to the Cargo file
[dependencies]
dynamic-struct = "0.1"
2) Add the derive macro to the struct and mark the properties that are dynamic
use dynamic_struct::Dynamic;
#[derive(Dynamic)]
struct Demo {
a: u32,
b: u32,
#[dynamic((a, b), calculate_c)]
c: u32,
}
impl Demo {
fn calculate_c(&mut self) {
self.c = self.a + self.b
}
}
The attribute for the properties has the following structure:
#[dynamic(tuple of dependent property names, name of local method name)]
The local method must have the call signature matching fn name(&mut self)
.
3) Update the properties using the generated mutate functions
fn main() {
let demo = Demo { a: 1, b: 2, c: 3 };
dbg!(demo.c); //3
demo.update_a(7);
dbg!(demo.c); //9
}
How it works
1) Functions are created to signal when a property is changed, it is populated with the methods that should be called.
impl Demo {
#[inline]
pub fn updated_a(&mut self) {
self.update_c();
}
}
Note: properties that do not propagate changes will still be created but will be empty.
2) Functions are created for each property to update the property
For non-dynamic properties, the value can be set via a parameter matching the field type, then the field updated function is called (listed above).
impl Demo {
#[inline]
pub fn update_a(&mut self, a: u32) {
self.a = a;
self.updated_a();
}
}
For dynamic properties, the value is set by calling the specified dynamic function, then the field updated function is called (listed above).
impl Demo {
#[inline]
pub fn update_c(&mut self) {
self.calculate_c();
self.updated_c();
}
}
Note: be careful not to create cyclic dependencies!
Configuration
The names of the generated functions can be customised by declaring a struct attribute and overriding a prefix/suffix. e.g:
#[derive(Dynamic)]
#[dynamic(setter_prefix = "set_", setter_suffix = "_value")]
struct MyStruct {
a: u32,
b: u32,
}
fn main() {
let test = MyStruct { a: 1, b: 2 };
test.set_a_value(3);
test.set_b_value(4);
}
Properties that can specified include:
Name | Type | Comment |
---|---|---|
updated_prefix | str | Prefix for updated methods |
updated_suffix | str | Suffix for updated methods |
setter_prefix | str | Prefix for setter methods (non-dynamic fields) |
setter_suffix | str | Suffix for setter methods (non-dynamic fields) |
update_prefix | str | Prefix for update methods (dynamic fields) |
update_suffix | str | Suffix for update methods (dynamic fields) |
Dependencies
~2MB
~42K SLoC