Skip to content

Instantly share code, notes, and snippets.

@mendes5
Created February 2, 2023 04:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mendes5/f80dadf823bd4ead46fa20d57ed29d75 to your computer and use it in GitHub Desktop.
Save mendes5/f80dadf823bd4ead46fa20d57ed29d75 to your computer and use it in GitHub Desktop.
Can reactive declarative programming in rust be easier?
#![feature(local_key_cell_methods)]
use std::{cell::RefCell, collections::HashMap};
// Core idea: Rust lets you do this:
fn create_closure(initial: u32) -> Box<dyn FnMut()> {
let mut captured = initial;
Box::new(move || {
captured += 1;
println!("Now captured is {}", captured);
})
}
// This is basically a closure allocator
// you can now call it multiple times
fn idea() {
let mut my_closure = create_closure(5);
my_closure(); // prints captured is 6
my_closure(); // prints captured is 7
my_closure(); // prints captured is 8
my_closure(); // prints captured is 9
my_closure(); // prints captured is 10
}
// which might easier to write than
/**
* struct Closure {
* captured: u32,
* }
*
* impl Closure {
* fn new(initial: u32) -> Self {
* Self {
* captured: initial,
* }
* }
* fn update(&mut self) {
* captured += 1;
*
* println!("Now captured is {}", captured);
* }
* }
*
* let mut my_closure = Closure::new(0);
* my_closure.update();
*
* Since you only need one funny function returning a closure.
*/
// Memo:
// * A heap allocated closure
// * That can be run more than once (it caputres its enviroment muttably)
// * Which returns a flag (to decide if the children should be rendered)
// * And another heap allocated closure
// Double closure is required since trying to call `mount_closure`
// on the same closure would panic due to consecutive borrows on the Tree's RefCell
type Closure = Box<dyn FnMut() -> (bool, Box<dyn FnMut()>)>;
thread_local! {
static LAST_ID: RefCell<u64> = RefCell::new(0);
// Owns pointers to the closures in the heap
// so we can update them again
static TREE: RefCell<HashMap<u64, Closure>> = RefCell::new(HashMap::new());
}
struct Props {
current: u32,
max: u32,
}
// Recursive component
fn component_example(props: Props) -> Closure {
// Hooks/State/Context/Props
let a = props.current;
Box::new(move || {
// Effects
println!("{} Im pringing {}", String::from(" ").repeat(a as usize), a);
(
true,
Box::new(move || {
// Children
if a < props.max {
mount_closure(component_example, Props { current: a + 1, max: props.max });
}
}),
)
})
}
// Similar to React.createElement
// receives component and props
// but dont actually executes them
fn mount_closure<T, A: Fn(T) -> Closure>(
component: A,
props: T,
) {
let next_id = LAST_ID.with_borrow_mut(|last| {
let next = *last;
*last += 1;
next
});
// Need a way to track unmounts
// and to reconcile it to an actual tree
// instead of a vec
TREE
// Do our mutation on TREE for this component
.with_borrow_mut(|cache| {
cache.insert(next_id, component(props));
cache.get_mut(&next_id).map(|f| f())
})
// Release the borrow then mount the children
.map(|(can_render, mut mounter)| {
if can_render {
mounter();
}
});
}
// What setState would do
// but to all components reacting to a given state or context
fn update_closure(id: u64) {
TREE.with_borrow_mut(|cache| {
if let Some(closure) = cache.get_mut(&id) {
let (can_render, mounter) = closure();
if can_render {
return Some(mounter);
}
}
None
}).map(|mut mount| {
mount();
});
}
fn main() {
idea();
// Congratulations sir, you just invented a slower stack:
// * Three heap allocations
// * Dynamic dispatch to next functions
// * Pointer indirections
mount_closure(component_example, Props { current: 0, max: 30 });
// But now all memory related to the computations are still stored on
// the heap. And i can choose to update a specifc closure (or component)
// and all closures that it mounts
//
// this might come in handy if we have something
// like use_state or use_context in the future
update_closure(15);
// The cost should be about the same of implementatinos using
// `struct ClosureX {...}; impl Component for ClosureX { mount/unmont/etc }`
// Since creating a `FnMut()` closures are basically an
// struct + associated function. Also in the example `ClosureX` would
// need to be `dyn` which means `Box<dyn Component>`, so it
// would need to be heap allocated anyways if we hope to interact
// with it again after the first call.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment