Closed jkelleyrtp closed 9 months ago
Right now we give out GenerationalRefs for CopyValues which give Read/Write handles. However, there's nothing really stopping us from giving out Context
where Context is Copy and derefs to an immutable value 100% of the time.
The read and write methods are unfortunately somewhat fundamental to the design of generational-box
. Currently, generational box allocates something like RefCell<Option<Box<dyn std::any::Any>>>
whenever it cannot recycle a box. We need the RefCell to allow us to reuse that box when the scope is dropped. CopyValue maps the ref to a concrete type, but it doesn't add any new refcell checking to the box.
We need to give out something with a drop handler when you call read to track when it is safe to recycle the box if a scope is dropped. If it has a drop handler it cannot implement the Copy
trait
We'd have to run the drop when the scope is dropped, like signals do today. I guess we could just hand out a CopyValue or GenerationalRef directly. Or wrap the GenerationalRef in a wrapper that doesn't expose write().
Primarily what I'm interested in supporting is being able to put values into context that aren't clone and then call methods on &self
. It would make exposing contexts like DesktopContext a simpler endeavor.
// no `Clone` bound necessary
struct Slider {
value: Signal<i32>,
increment: Signal<i32>,
data: SomeBigNoCloneData
}
// to initialize
use_context_provider::<Slider>();
// to use
let slider_value = consume_context::<Slider>().value();
We'd have to run the drop when the scope is dropped, like signals do today.
You need to check that it is OK to drop the scope in case you have something like this with the deref behavior:
let copy_deref = Copy::new(String::new());
let raw_value: &String = copy_deref.deref();
// scope drops
// We have no way of telling the reference is no longer alive at runtime
println!("{raw_value}");
I guess we could just hand out a CopyValue or GenerationalRef directly. Or wrap the GenerationalRef in a wrapper that doesn't expose write().
We could but it makes it worse if you try to provide a signal, struct of signals or something else that is already copy:
let signal = use_context_provider(Signal::new(0));
// in a child
let signal = use_context::<Signal<i32>>();
// if we wrap in CopyValue
let real_value = signal.read().read()
// if we give out GenerationalRef
spawn(async move {
/// Causes lifetime error?
signal.read()
});
Specific Demand
Right now we give out GenerationalRefs for CopyValues which give Read/Write handles. However, there's nothing really stopping us from giving out
Context<T>
whereContext
is Copy and derefs to an immutable value 100% of the time.This would be useful for the Context API - allowing us to not need wrapping of contexts in Rc to get the
Clone
semantics.We could do this by adding new types or just giving out Context in consume_context.
This would have slightly weird semantics when trying to bridge between virtualdoms like in fullstack. However, the solution for fullstack might just be imposing a clone bound or setting up the arenas before launching new virtualdoms.
Ergonomics
The ergonomics would be much better than now.
It would also relax the clone bound on contexts which is just generally useful since contexts don't really need to be clone.
Issues
The main issue is sending contexts into environments like new threads where you would expect it to work, but it doesn't.
Currently this is only fullstack and multiwindow. The final design of multiwindow is not super thought-out today, but the goal would be to share parts of a single virtualdom into multiple windows, and that's currently not multithread-able. We use lots of Cell/Rc already, so for truly multithreaded windows, our current context solution is also not tenable.
A number of our structures currently impl Send/Sync when in reality they become useless on separate threads. We should probably disable their Send/Sync abilities.
nb
This would be pretty easy to add to #1791