diff --git a/.vscode/settings.json b/.vscode/settings.json index 80f51cff8..2f418701b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } \ No newline at end of file diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index 493ea4601..d65500362 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -18,3 +18,7 @@ Jemalloc Cloudfare webapps downcasting +vec +deref +derefs +Derefing diff --git a/SOLVEDPROBLEMS.md b/SOLVEDPROBLEMS.md index 2b1c7f903..f9e94c753 100644 --- a/SOLVEDPROBLEMS.md +++ b/SOLVEDPROBLEMS.md @@ -1,5 +1,16 @@ # Solved problems while building Dioxus +focuses: +- ergonomics +- render agnostic +- remote coupling +- memory efficient +- concurrent +- global context +- scheduled updates +- + + ## FC Macro for more elegant components Originally the syntax of the FC macro was meant to look like: @@ -160,14 +171,77 @@ At runtime, the new closure is created that captures references to `ctx`. Theref ## Context and lifetimes -> SAFETY hole We want components to be able to fearlessly "use_context" for use in state management solutions. However, we cannot provide these guarantees without compromising the references. If a context mutates, it cannot lend out references. -Functionally, this can be solved with UnsafeCell and runtime dynamics. Essentially, if a context mutates, then any affected components would need -to be updated, even if they themselves aren't updated. Otherwise, a handler would be pointing to +Functionally, this can be solved with UnsafeCell and runtime dynamics. Essentially, if a context mutates, then any affected components would need to be updated, even if they themselves aren't updated. Otherwise, a reference would be pointing at data that could have potentially been moved. -This can be enforced by us or by implementers. +To do this safely is a pretty big challenge. We need to provide a method of sharing data that is safe, ergonomic, and that fits the abstraction model. +Enter, the "ContextGuard". + +The "ContextGuard" is very similar to a Ref/RefMut from the RefCell implementation, but one that derefs into actual underlying value. + +However, derefs of the ContextGuard are a bit more sophisticated than the Ref model. + +For RefCell, when a Ref is taken, the RefCell is now "locked." This means you cannot take another `borrow_mut` instance while the Ref is still active. For our purposes, our modification phase is very limited, so we can make more assumptions about what is safe. + +1. We can pass out ContextGuards from any use of use_context. These don't actually lock the value until used. +2. The ContextGuards only lock the data while the component is executing and when a callback is running. +3. Modifications of the underlying context occur after a component is rendered and after the event has been run. + +With the knowledge that usage of ContextGuard can only be achieved in a component context and the above assumptions, we can design a guard that prevents any poor usage but also is ergonomic. + +As such, the design of the ContextGuard must: +- be /copy/ for easy moves into closures +- never point to invalid data (no dereferencing of raw pointers after movable data has been changed (IE a vec has been resized)) +- not allow references of underlying data to leak into closures + +To solve this, we can be clever with lifetimes to ensure that any data is protected, but context is still ergonomic. + +1. As such, deref context guard returns an element with a lifetime bound to the borrow of the guard. +2. Because we cannot return locally borrowed data AND we consume context, this borrow cannot be moved into a closure. +3. ContextGuard is *copy* so the guard itself can be moved into closures +4. ContextGuard derefs with its unique lifetime *inside* closures +5. Derefing a ContextGuard evaluates the underlying selector to ensure safe temporary access to underlying data + +```rust +struct ExampleContext { + // unpinnable objects with dynamic sizing + items: Vec +} + +fn Example<'src>(ctx: Context<'src, ()>) -> VNode<'src> { + let val: &'b ContextGuard = (&'b ctx).use_context(|context: &'other ExampleContext| { + // always select the last element + context.items.last() + }); + + let handler1 = move |_| println!("Value is {}", val); // deref coercion performed here for printing + let handler2 = move |_| println!("Value is {}", val); // deref coercion performed here for printing + + ctx.view(html! { +
+ + +
+

"Value is: {val}"

+
+
+ }) +} +``` + +A few notes: +- this does *not* protect you from data races!!! +- this does *not* force rendering of components +- this *does* protect you from invalid + UB use of data +- this approach leaves lots of room for fancy state management libraries +- this approach is fairly quick, especially if borrows can be cached during usage phases + + +## Concurrency + +I don't even know yet diff --git a/examples/example_app.rs b/examples/example_app.rs index 51c4957b6..bd7e42de9 100644 --- a/examples/example_app.rs +++ b/examples/example_app.rs @@ -6,8 +6,54 @@ //! //! cargo run --features dioxus/static +use std::u32; + +use dioxus::prelude::Context; + +// #[allow(unused_lifetimes)] +#[derive(Debug, PartialEq, Hash)] +struct Pool<'a> { + a: u32, + b: &'a str, +} + +struct UserData {} +type Query = fn(&Pool, In) -> Result; +// type Query = fn(&Pool, In) -> Out; + +static GET_USER: Query> = |pool, name| { + // + let b = Ok(())?; + let b = Ok(())?; + let b = Ok(())?; + let b = Ok(())?; + let b = Ok(())?; + let b = Ok(())?; + todo!() +}; + +static SET_USER: Query> = |pool, name| { + // + todo!() +}; + fn main() { - // DioxusApp::new(launch) + // // returns a future + // let user_data = use_db(&ctx, GET_USER, || "Bill"); + + // use_try_suspense(&ctx, async move { + // match user_data.await? { + // Ok() => {} + // Err(err) => {} + // } + // }) + // } + + // fn use_try_suspense(ctx: &Context<()>) { + // let c: Result<(), ()> = { + // // let b = Ok(()); + // // b? + // }; } mod launches { diff --git a/packages/core/examples/contextapi.rs b/packages/core/examples/contextapi.rs new file mode 100644 index 000000000..b6e82f117 --- /dev/null +++ b/packages/core/examples/contextapi.rs @@ -0,0 +1,71 @@ +use std::{marker::PhantomData, ops::Deref}; + +use builder::{button, div}; +use dioxus_core::prelude::*; + +fn main() {} +struct SomeContext { + items: Vec, +} +/* +desired behavior: + +free to move the context guard around +not free to move contents of context guard into closure + +rules: +can deref in a function +cannot drag the refs into the closure w +*/ + +static Example: FC<()> = |ctx| { + let value = use_context(&ctx, |ctx: &SomeContext| ctx.items.last().unwrap()); + + // let b = *value; + // let v2 = *value; + let cb = move |e| { + // let g = b.as_str(); + // let g = (v2).as_str(); + let g = (value).as_str(); + // let g = b.as_str(); + }; + // let r = *value; + // let r2 = *r; + + ctx.view(|bump| { + button(bump) + .listeners([builder::on(bump, "click", cb)]) + .finish() + }) + // ctx.view(html! { + //
+ //
+ // }) +}; + +#[derive(Clone, Copy)] +struct ContextGuard { + val: PhantomData, +} + +impl<'a, T> Deref for ContextGuard { + type Target = T; + + fn deref(&self) -> &Self::Target { + todo!() + } +} + +fn use_context<'scope, 'dope, 'a, P: Properties, I, O: 'a>( + ctx: &'scope Context

, + s: fn(&'a I) -> O, +) -> &'scope ContextGuard { + // ) -> &'scope ContextGuard { + todo!() +} diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 6015bdb73..0c4a23ee4 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1068,9 +1068,10 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> { /// // do something when a click happens... /// }); /// ``` -pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a> +pub fn on<'a, 'b, F: 'b>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a> where - F: Fn(()) + 'a, + 'b: 'a + 'static, + F: Fn(()) + 'b, { Listener { event, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d1d1c03b1..0d68f4a17 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -174,7 +174,11 @@ mod velement { } /// An event listener. - pub struct Listener<'a> { + pub struct Listener<'a> +// pub struct Listener<'a, 'b> + // where + // 'b: 'a + 'static, + { /// The type of event to listen for. pub(crate) event: &'a str, /// The callback to invoke when the event happens.