Rust language is the only compiled language without gc with guaranteed memory safety.
Using new Forget
(must drop) marker trait, I propose safe, performant, ergonomic event and fine grained reactivity system that solves these problems.
Which only work in rust due to rust's unique properties.
Forget
or Leak
trait is a marker trait for types cannot be forgotten (must run destructor).
Rust doesn't have these traits now, but proposed as a solution for scoped task trilemma.
There is a good Forget
marker trait RFC. Please read this before proceed.
Let's say we have drop guarantee in rust.
We can have safe collection type where inserted items' lifetimes are smaller than the collection using drop guarantee.
Then we can have a safe, borrow check friendly event system where listeners are non 'static
and detach itself on drop (safety requirement) like
let target = pin!(<EventTarget!(&mut i32)>::new());
{
let mut b = 2;
// listener borrows local variable `b`
let listener = pin!(Listener::new(|a: &mut i32| {
dbg!(*a + b);
*a += 1;
b += 2;
}));
// Bind listener
target.bind(listener);
// Emit event (*a + b = 7 is printed)
target.emit(&mut 5);
// Listener drops before exiting scope, removed from `target`
}
// Emit event but nothing happens as there are no listeners.
target.emit(&mut 0);
Note
Due to lack of Unforgettable types, they are not completely safe (although there are no unsafe blocks in example).
With these event system, we can create high performance reactivity system on top of it. Please read about the prototype before proceed from here.
let a = pin!(State::new(0));
let b = pin!(State::new(0));
let_effect!({
b.set(a.get($));
println!("b is updated because a is changed");
});
let_effect
is a macro for creating a new effect.
The effect run whenever the local variable a
changes. We calculate and place number of required bindings in the effect using $
sign. Notice the block inside let_effect
is closure. Because we can use local references, it is also very easy to split long functions into separated.
Okay, so we have reactivity and event system. But they all drop on the end of the scope! To solve the problems we need self referential type, but how?
We already have safe self referential in rust.
Self referential in rust:
// fut is self referential
let fut = async {
let a = 1;
let b = &b;
async {}.await;
// a is captured due to await
// b is a reference but also captured!
dbg!(b);
};
Inspired by AsyncUI, we can create gui component combined with event and reactivity system using async.
async fn main() {
// run application with a root component
run(container).await;
}
struct Color {
r: u8,
g: u8,
b: u8
}
// Container component with two colored rect
async fn container(ui: Ui) {
let counter = pin!(State::new(0));
let color1 = pin!(State::new(Color {
r: 255,
g: 255,
b: 255,
}));
let color2 = pin!(State::new(Color {
r: 0,
g: 0,
b: 255,
}));
let_effect!({
println!("counter is updated to {}", counter.get($));
});
// use Future combinator
join!(
colored_rect(
color1,
pin!(Listener::new(|| counter.update(|prev| prev + 1)),
).show(ui.clone()),
colored_rect(
color2,
pin!(Listener::new(|| counter.update(|prev| prev - 1)),
).show(ui),
);
}
// custom sized rect component
fn colored_rect(
color: Pin<&State<Color>>,
on_click: Pin<&Listener<impl FnMut()>>,
) -> impl Component<Output = ()> {
async |ui| {
let size = pin!(State::new((50, 50));
Rect::builder()
.size(size)
.color(color)
.on_click(on_click)
.build()
.show(ui).await
}
}
Output:
- Using async blocks and closures remove a lot of boilerplates of declaring self referential structs. But still involves a lot of interior mutability. Is it possible to remove them while keeping its convenience?
- Pin projection boilerplates and ergonomics. rust-lang/rust#130494