Selaa lähdekoodia

Fix the issue of duplicate unique ID for atoms using `newtype`.

The MergeFunctionsPass in LLVM merges identical functions (https://rust.godbolt.org/z/3nnr9nMne), resulting in the same function addresses.
DianQK 2 vuotta sitten
vanhempi
commit
535435a4cf

+ 3 - 1
.github/workflows/macos.yml

@@ -33,4 +33,6 @@ jobs:
       - uses: dtolnay/rust-toolchain@stable
       - uses: Swatinem/rust-cache@v2
       - uses: actions/checkout@v3
-      - run: cargo test --all --tests
+      - run: |
+          cargo test --all --tests
+          cargo test --package fermi --release

+ 1 - 0
.github/workflows/windows.yml

@@ -85,4 +85,5 @@ jobs:
           set RUST_BACKTRACE=1
           cargo build --all --tests --examples
           cargo test --all --tests
+          cargo test --package fermi --release
         shell: cmd

+ 2 - 2
docs/guide/src/en/__unused/advanced-guides/12-signals.md

@@ -95,7 +95,7 @@ Calling `deref` or `deref_mut` is actually more complex than it seems. When a va
 Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
 
 ```rust
-const TITLE: Atom<String> = || "".to_string();
+const TITLE: Atom<String> = Atom(|| "".to_string());
 const Provider: Component = |cx|{
     let title = use_signal(cx, &TITLE);
     render!(input { value: title })
@@ -131,7 +131,7 @@ By default, Dioxus is limited when you use iter/map. With the `For` component, y
 Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
 
 ```rust
-const DICT: AtomFamily<String, String> = |_| {};
+const DICT: AtomFamily<String, String> = AtomFamily(|_| {});
 const List: Component = |cx|{
     let dict = use_signal(cx, &DICT);
     cx.render(rsx!(

+ 4 - 4
docs/guide/src/en/async/use_coroutine.md

@@ -146,7 +146,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
 We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
 
 ```rust
-static USERNAME: Atom<String> = |_| "default".to_string();
+static USERNAME: Atom<String> = Atom(|_| "default".to_string());
 
 fn app(cx: Scope) -> Element {
     let atoms = use_atom_root(cx);
@@ -159,7 +159,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Banner(cx: Scope) -> Element {
-    let username = use_read(cx, USERNAME);
+    let username = use_read(cx, &USERNAME);
 
     cx.render(rsx!{
         h1 { "Welcome back, {username}" }
@@ -177,8 +177,8 @@ enum SyncAction {
 }
 
 async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
-    let username = atoms.write(USERNAME);
-    let errors = atoms.write(ERRORS);
+    let username = atoms.write(&USERNAME);
+    let errors = atoms.write(&ERRORS);
 
     while let Ok(msg) = rx.next().await {
         match msg {

+ 3 - 3
docs/guide/src/en/best_practices/error_handling.md

@@ -118,14 +118,14 @@ enum InputError {
     TooShort,
 }
 
-static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
+static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
 ```
 
 Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
 
 ```rust
 fn TopLevel(cx: Scope) -> Element {
-    let error = use_read(cx, INPUT_ERROR);
+    let error = use_read(cx, &INPUT_ERROR);
 
     match error {
         TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@@ -139,7 +139,7 @@ Now, whenever a downstream component has an error in its actions, it can simply
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let set_error = use_set(cx, INPUT_ERROR);
+    let set_error = use_set(cx, &INPUT_ERROR);
 
     cx.render(rsx!{
         input {

+ 4 - 4
docs/guide/src/pt-br/async/use_coroutine.md

@@ -105,7 +105,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
 Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` – os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface.
 
 ```rust
-static USERNAME: Atom<String> = |_| "default".to_string();
+static USERNAME: Atom<String> = Atom(|_| "default".to_string());
 
 fn app(cx: Scope) -> Element {
     let atoms = use_atom_root(cx);
@@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Banner(cx: Scope) -> Element {
-    let username = use_read(cx, USERNAME);
+    let username = use_read(cx, &USERNAME);
 
     cx.render(rsx!{
         h1 { "Welcome back, {username}" }
@@ -134,8 +134,8 @@ enum SyncAction {
 }
 
 async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
-    let username = atoms.write(USERNAME);
-    let errors = atoms.write(ERRORS);
+    let username = atoms.write(&USERNAME);
+    let errors = atoms.write(&ERRORS);
 
     while let Ok(msg) = rx.next().await {
         match msg {

+ 3 - 3
docs/guide/src/pt-br/best_practices/error_handling.md

@@ -113,14 +113,14 @@ enum InputError {
     TooShort,
 }
 
-static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
+static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
 ```
 
 Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
 
 ```rust
 fn TopLevel(cx: Scope) -> Element {
-    let error = use_read(cx, INPUT_ERROR);
+    let error = use_read(cx, &INPUT_ERROR);
 
     match error {
         TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele
 
 ```rust
 fn Commandline(cx: Scope) -> Element {
-    let set_error = use_set(cx, INPUT_ERROR);
+    let set_error = use_set(cx, &INPUT_ERROR);
 
     cx.render(rsx!{
         input {

+ 5 - 5
examples/fermi.rs

@@ -7,11 +7,11 @@ fn main() {
     dioxus_desktop::launch(app)
 }
 
-static NAME: Atom<String> = |_| "world".to_string();
+static NAME: Atom<String> = Atom(|_| "world".to_string());
 
 fn app(cx: Scope) -> Element {
     use_init_atom_root(cx);
-    let name = use_read(cx, NAME);
+    let name = use_read(cx, &NAME);
 
     cx.render(rsx! {
         div { "hello {name}!" }
@@ -21,7 +21,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Child(cx: Scope) -> Element {
-    let set_name = use_set(cx, NAME);
+    let set_name = use_set(cx, &NAME);
 
     cx.render(rsx! {
         button {
@@ -31,10 +31,10 @@ fn Child(cx: Scope) -> Element {
     })
 }
 
-static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()];
+static NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);
 
 fn ChildWithRef(cx: Scope) -> Element {
-    let names = use_atom_ref(cx, NAMES);
+    let names = use_atom_ref(cx, &NAMES);
 
     cx.render(rsx! {
         div {

+ 10 - 10
packages/fermi/README.md

@@ -1,4 +1,3 @@
-
 <div align="center">
   <h1>Fermi ⚛</h1>
   <p>
@@ -6,7 +5,6 @@
   </p>
 </div>
 
-
 <div align="center">
   <!-- Crates version -->
   <a href="https://crates.io/crates/dioxus">
@@ -30,21 +28,21 @@
   </a>
 </div>
 
------
+---
 
 Fermi is a global state management solution for Dioxus that's as easy as `use_state`.
 
 Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
 
 ```rust, ignore
-static NAME: Atom<&str> = |_| "Dioxus";
+static NAME: Atom<&str> = Atom(|_| "Dioxus");
 ```
 
 From anywhere in our app, we can read the value of our atom:
 
-```rust, ignores
+```rust, ignore
 fn NameCard(cx: Scope) -> Element {
-    let name = use_read(cx, NAME);
+    let name = use_read(cx, &NAME);
     cx.render(rsx!{ h1 { "Hello, {name}"} })
 }
 ```
@@ -53,7 +51,7 @@ We can also set the value of our atom, also from anywhere in our app:
 
 ```rust, ignore
 fn NameCard(cx: Scope) -> Element {
-    let set_name = use_set(cx, NAME);
+    let set_name = use_set(cx, &NAME);
     cx.render(rsx!{
         button {
             onclick: move |_| set_name("Fermi"),
@@ -66,10 +64,10 @@ fn NameCard(cx: Scope) -> Element {
 If needed, we can update the atom's value, based on itself:
 
 ```rust, ignore
-static COUNT: Atom<i32> = |_| 0;
+static COUNT: Atom<i32> = Atom(|_| 0);
 
 fn Counter(cx: Scope) -> Element {
-    let mut count = use_atom_state(cx, COUNT);
+    let mut count = use_atom_state(cx, &COUNT);
 
     cx.render(rsx!{
         p {
@@ -86,6 +84,7 @@ fn Counter(cx: Scope) -> Element {
 It's that simple!
 
 ## Installation
+
 Fermi is currently under construction, so you have to use the `master` branch to get started.
 
 ```toml
@@ -93,10 +92,10 @@ Fermi is currently under construction, so you have to use the `master` branch to
 fermi = { git = "https://github.com/dioxuslabs/dioxus" }
 ```
 
-
 ## Running examples
 
 The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
+
 ```sh
 $ cargo run --example fermi
 ```
@@ -104,6 +103,7 @@ $ cargo run --example fermi
 ## Features
 
 Broadly our feature set required to be released includes:
+
 - [x] Support for Atoms
 - [x] Support for AtomRef (for values that aren't `Clone`)
 - [ ] Support for Atom Families

+ 23 - 7
packages/fermi/src/atoms/atom.rs

@@ -1,21 +1,21 @@
 use crate::{AtomId, AtomRoot, Readable, Writable};
 
-pub type Atom<T> = fn(AtomBuilder) -> T;
+pub struct Atom<T>(pub fn(AtomBuilder) -> T);
 pub struct AtomBuilder;
 
-impl<V> Readable<V> for Atom<V> {
+impl<V> Readable<V> for &'static Atom<V> {
     fn read(&self, _root: AtomRoot) -> Option<V> {
         todo!()
     }
     fn init(&self) -> V {
-        (*self)(AtomBuilder)
+        self.0(AtomBuilder)
     }
     fn unique_id(&self) -> AtomId {
-        *self as *const ()
+        *self as *const Atom<V> as *const ()
     }
 }
 
-impl<V> Writable<V> for Atom<V> {
+impl<V> Writable<V> for &'static Atom<V> {
     fn write(&self, _root: AtomRoot, _value: V) {
         todo!()
     }
@@ -23,6 +23,22 @@ impl<V> Writable<V> for Atom<V> {
 
 #[test]
 fn atom_compiles() {
-    static TEST_ATOM: Atom<&str> = |_| "hello";
-    dbg!(TEST_ATOM.init());
+    static TEST_ATOM: Atom<&str> = Atom(|_| "hello");
+    dbg!((&TEST_ATOM).init());
+}
+
+#[test]
+fn atom_is_unique() {
+    static TEST_ATOM_1: Atom<&str> = Atom(|_| "hello");
+    static TEST_ATOM_2: Atom<&str> = Atom(|_| "hello");
+    assert_eq!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_1).unique_id());
+    assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
+}
+
+#[test]
+fn atom_is_unique_2() {
+    struct S(String);
+    static TEST_ATOM_1: Atom<Vec<S>> = Atom(|_| Vec::new());
+    static TEST_ATOM_2: Atom<Vec<String>> = Atom(|_| Vec::new());
+    assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
 }

+ 5 - 5
packages/fermi/src/atoms/atomfamily.rs

@@ -2,23 +2,23 @@ use crate::{AtomId, AtomRoot, Readable, Writable};
 use im_rc::HashMap as ImMap;
 
 pub struct AtomFamilyBuilder;
-pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
+pub struct AtomFamily<K, V>(pub fn(AtomFamilyBuilder) -> ImMap<K, V>);
 
-impl<K, V> Readable<ImMap<K, V>> for AtomFamily<K, V> {
+impl<K, V> Readable<ImMap<K, V>> for &'static AtomFamily<K, V> {
     fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
         todo!()
     }
 
     fn init(&self) -> ImMap<K, V> {
-        (*self)(AtomFamilyBuilder)
+        self.0(AtomFamilyBuilder)
     }
 
     fn unique_id(&self) -> AtomId {
-        *self as *const ()
+        *self as *const AtomFamily<K, V> as *const ()
     }
 }
 
-impl<K, V> Writable<ImMap<K, V>> for AtomFamily<K, V> {
+impl<K, V> Writable<ImMap<K, V>> for &'static AtomFamily<K, V> {
     fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
         todo!()
     }

+ 6 - 6
packages/fermi/src/atoms/atomref.rs

@@ -2,24 +2,24 @@ use crate::{AtomId, AtomRoot, Readable};
 use std::cell::RefCell;
 
 pub struct AtomRefBuilder;
-pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
+pub struct AtomRef<T>(pub fn(AtomRefBuilder) -> T);
 
-impl<V> Readable<RefCell<V>> for AtomRef<V> {
+impl<V> Readable<RefCell<V>> for &'static AtomRef<V> {
     fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
         todo!()
     }
 
     fn init(&self) -> RefCell<V> {
-        RefCell::new((*self)(AtomRefBuilder))
+        RefCell::new(self.0(AtomRefBuilder))
     }
 
     fn unique_id(&self) -> AtomId {
-        *self as *const ()
+        *self as *const AtomRef<V> as *const ()
     }
 }
 
 #[test]
 fn atom_compiles() {
-    static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
-    dbg!(TEST_ATOM.init());
+    static TEST_ATOM: AtomRef<Vec<String>> = AtomRef(|_| vec![]);
+    dbg!((&TEST_ATOM).init());
 }

+ 4 - 1
packages/fermi/src/hooks/atom_ref.rs

@@ -13,7 +13,10 @@ use std::{
 ///
 ///
 ///
-pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
+pub fn use_atom_ref<'a, T: 'static>(
+    cx: &'a ScopeState,
+    atom: &'static AtomRef<T>,
+) -> &'a UseAtomRef<T> {
     let root = use_atom_root(cx);
 
     &cx.use_hook(|| {

+ 1 - 1
packages/fermi/src/hooks/state.rs

@@ -19,7 +19,7 @@ use std::{
 /// static COUNT: Atom<u32> = |_| 0;
 ///
 /// fn Example(cx: Scope) -> Element {
-///     let mut count = use_atom_state(cx, COUNT);
+///     let mut count = use_atom_state(cx, &COUNT);
 ///
 ///     cx.render(rsx! {
 ///         div {