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.
This commit is contained in:
DianQK committed 2023-06-21 21:50:10 +08:00
1 parent 9731640434
commit 535435a4cf
14 files changed
+74 -52

No files matched your search

+3 -1
View File
@@ -33,4 +33,6 @@ jobs:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: cargo test --all --tests - run: |
cargo test --all --tests
cargo test --package fermi --release
+1
View File
@@ -85,4 +85,5 @@ jobs:
set RUST_BACKTRACE=1 set RUST_BACKTRACE=1
cargo build --all --tests --examples cargo build --all --tests --examples
cargo test --all --tests cargo test --all --tests
cargo test --package fermi --release
shell: cmd shell: cmd
@@ -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. 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 ```rust
const TITLE: Atom<String> = || "".to_string(); const TITLE: Atom<String> = Atom(|| "".to_string());
const Provider: Component = |cx|{ const Provider: Component = |cx|{
let title = use_signal(cx, &TITLE); let title = use_signal(cx, &TITLE);
render!(input { value: 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. 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 ```rust
const DICT: AtomFamily<String, String> = |_| {}; const DICT: AtomFamily<String, String> = AtomFamily(|_| {});
const List: Component = |cx|{ const List: Component = |cx|{
let dict = use_signal(cx, &DICT); let dict = use_signal(cx, &DICT);
cx.render(rsx!( cx.render(rsx!(
+4 -4
View File
@@ -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. 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 ```rust
static USERNAME: Atom<String> = |_| "default".to_string(); static USERNAME: Atom<String> = Atom(|_| "default".to_string());
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let atoms = use_atom_root(cx); let atoms = use_atom_root(cx);
@@ -159,7 +159,7 @@ fn app(cx: Scope) -> Element {
} }
fn Banner(cx: Scope) -> Element { fn Banner(cx: Scope) -> Element {
let username = use_read(cx, USERNAME); let username = use_read(cx, &USERNAME);
cx.render(rsx!{ cx.render(rsx!{
h1 { "Welcome back, {username}" } h1 { "Welcome back, {username}" }
@@ -177,8 +177,8 @@ enum SyncAction {
} }
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) { async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
let username = atoms.write(USERNAME); let username = atoms.write(&USERNAME);
let errors = atoms.write(ERRORS); let errors = atoms.write(&ERRORS);
while let Ok(msg) = rx.next().await { while let Ok(msg) = rx.next().await {
match msg { match msg {
@@ -118,14 +118,14 @@ enum InputError {
TooShort, 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. Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
```rust ```rust
fn TopLevel(cx: Scope) -> Element { fn TopLevel(cx: Scope) -> Element {
let error = use_read(cx, INPUT_ERROR); let error = use_read(cx, &INPUT_ERROR);
match error { match error {
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }), 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 ```rust
fn Commandline(cx: Scope) -> Element { fn Commandline(cx: Scope) -> Element {
let set_error = use_set(cx, INPUT_ERROR); let set_error = use_set(cx, &INPUT_ERROR);
cx.render(rsx!{ cx.render(rsx!{
input { input {
+4 -4
View File
@@ -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. 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 ```rust
static USERNAME: Atom<String> = |_| "default".to_string(); static USERNAME: Atom<String> = Atom(|_| "default".to_string());
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let atoms = use_atom_root(cx); let atoms = use_atom_root(cx);
@@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
} }
fn Banner(cx: Scope) -> Element { fn Banner(cx: Scope) -> Element {
let username = use_read(cx, USERNAME); let username = use_read(cx, &USERNAME);
cx.render(rsx!{ cx.render(rsx!{
h1 { "Welcome back, {username}" } h1 { "Welcome back, {username}" }
@@ -134,8 +134,8 @@ enum SyncAction {
} }
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) { async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
let username = atoms.write(USERNAME); let username = atoms.write(&USERNAME);
let errors = atoms.write(ERRORS); let errors = atoms.write(&ERRORS);
while let Ok(msg) = rx.next().await { while let Ok(msg) = rx.next().await {
match msg { match msg {
@@ -113,14 +113,14 @@ enum InputError {
TooShort, 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. Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
```rust ```rust
fn TopLevel(cx: Scope) -> Element { fn TopLevel(cx: Scope) -> Element {
let error = use_read(cx, INPUT_ERROR); let error = use_read(cx, &INPUT_ERROR);
match error { match error {
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }), 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 ```rust
fn Commandline(cx: Scope) -> Element { fn Commandline(cx: Scope) -> Element {
let set_error = use_set(cx, INPUT_ERROR); let set_error = use_set(cx, &INPUT_ERROR);
cx.render(rsx!{ cx.render(rsx!{
input { input {
+5 -5
View File
@@ -7,11 +7,11 @@ fn main() {
dioxus_desktop::launch(app) dioxus_desktop::launch(app)
} }
static NAME: Atom<String> = |_| "world".to_string(); static NAME: Atom<String> = Atom(|_| "world".to_string());
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
use_init_atom_root(cx); use_init_atom_root(cx);
let name = use_read(cx, NAME); let name = use_read(cx, &NAME);
cx.render(rsx! { cx.render(rsx! {
div { "hello {name}!" } div { "hello {name}!" }
@@ -21,7 +21,7 @@ fn app(cx: Scope) -> Element {
} }
fn Child(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! { cx.render(rsx! {
button { 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 { fn ChildWithRef(cx: Scope) -> Element {
let names = use_atom_ref(cx, NAMES); let names = use_atom_ref(cx, &NAMES);
cx.render(rsx! { cx.render(rsx! {
div { div {
+10 -10
View File
@@ -1,4 +1,3 @@
<div align="center"> <div align="center">
<h1>Fermi ⚛</h1> <h1>Fermi ⚛</h1>
<p> <p>
@@ -6,7 +5,6 @@
</p> </p>
</div> </div>
<div align="center"> <div align="center">
<!-- Crates version --> <!-- Crates version -->
<a href="https://crates.io/crates/dioxus"> <a href="https://crates.io/crates/dioxus">
@@ -30,21 +28,21 @@
</a> </a>
</div> </div>
----- ---
Fermi is a global state management solution for Dioxus that's as easy as `use_state`. 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`: Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
```rust, ignore ```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: From anywhere in our app, we can read the value of our atom:
```rust, ignores ```rust, ignore
fn NameCard(cx: Scope) -> Element { fn NameCard(cx: Scope) -> Element {
let name = use_read(cx, NAME); let name = use_read(cx, &NAME);
cx.render(rsx!{ h1 { "Hello, {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 ```rust, ignore
fn NameCard(cx: Scope) -> Element { fn NameCard(cx: Scope) -> Element {
let set_name = use_set(cx, NAME); let set_name = use_set(cx, &NAME);
cx.render(rsx!{ cx.render(rsx!{
button { button {
onclick: move |_| set_name("Fermi"), 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: If needed, we can update the atom's value, based on itself:
```rust, ignore ```rust, ignore
static COUNT: Atom<i32> = |_| 0; static COUNT: Atom<i32> = Atom(|_| 0);
fn Counter(cx: Scope) -> Element { fn Counter(cx: Scope) -> Element {
let mut count = use_atom_state(cx, COUNT); let mut count = use_atom_state(cx, &COUNT);
cx.render(rsx!{ cx.render(rsx!{
p { p {
@@ -86,6 +84,7 @@ fn Counter(cx: Scope) -> Element {
It's that simple! It's that simple!
## Installation ## Installation
Fermi is currently under construction, so you have to use the `master` branch to get started. Fermi is currently under construction, so you have to use the `master` branch to get started.
```toml ```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" } fermi = { git = "https://github.com/dioxuslabs/dioxus" }
``` ```
## Running examples ## Running examples
The examples here use Dioxus Desktop to showcase their functionality. To run an example, use The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
```sh ```sh
$ cargo run --example fermi $ cargo run --example fermi
``` ```
@@ -104,6 +103,7 @@ $ cargo run --example fermi
## Features ## Features
Broadly our feature set required to be released includes: Broadly our feature set required to be released includes:
- [x] Support for Atoms - [x] Support for Atoms
- [x] Support for AtomRef (for values that aren't `Clone`) - [x] Support for AtomRef (for values that aren't `Clone`)
- [ ] Support for Atom Families - [ ] Support for Atom Families
+23 -7
View File
@@ -1,21 +1,21 @@
use crate::{AtomId, AtomRoot, Readable, Writable}; use crate::{AtomId, AtomRoot, Readable, Writable};
pub type Atom<T> = fn(AtomBuilder) -> T; pub struct Atom<T>(pub fn(AtomBuilder) -> T);
pub struct AtomBuilder; 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> { fn read(&self, _root: AtomRoot) -> Option<V> {
todo!() todo!()
} }
fn init(&self) -> V { fn init(&self) -> V {
(*self)(AtomBuilder) self.0(AtomBuilder)
} }
fn unique_id(&self) -> AtomId { 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) { fn write(&self, _root: AtomRoot, _value: V) {
todo!() todo!()
} }
@@ -23,6 +23,22 @@ impl<V> Writable<V> for Atom<V> {
#[test] #[test]
fn atom_compiles() { fn atom_compiles() {
static TEST_ATOM: Atom<&str> = |_| "hello"; static TEST_ATOM: Atom<&str> = Atom(|_| "hello");
dbg!(TEST_ATOM.init()); 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
View File
@@ -2,23 +2,23 @@ use crate::{AtomId, AtomRoot, Readable, Writable};
use im_rc::HashMap as ImMap; use im_rc::HashMap as ImMap;
pub struct AtomFamilyBuilder; 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>> { fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
todo!() todo!()
} }
fn init(&self) -> ImMap<K, V> { fn init(&self) -> ImMap<K, V> {
(*self)(AtomFamilyBuilder) self.0(AtomFamilyBuilder)
} }
fn unique_id(&self) -> AtomId { 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>) { fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
todo!() todo!()
} }
+6 -6
View File
@@ -2,24 +2,24 @@ use crate::{AtomId, AtomRoot, Readable};
use std::cell::RefCell; use std::cell::RefCell;
pub struct AtomRefBuilder; 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>> { fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
todo!() todo!()
} }
fn init(&self) -> RefCell<V> { fn init(&self) -> RefCell<V> {
RefCell::new((*self)(AtomRefBuilder)) RefCell::new(self.0(AtomRefBuilder))
} }
fn unique_id(&self) -> AtomId { fn unique_id(&self) -> AtomId {
*self as *const () *self as *const AtomRef<V> as *const ()
} }
} }
#[test] #[test]
fn atom_compiles() { fn atom_compiles() {
static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![]; static TEST_ATOM: AtomRef<Vec<String>> = AtomRef(|_| vec![]);
dbg!(TEST_ATOM.init()); dbg!((&TEST_ATOM).init());
} }
+4 -1
View File
@@ -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); let root = use_atom_root(cx);
&cx.use_hook(|| { &cx.use_hook(|| {
+1 -1
View File
@@ -19,7 +19,7 @@ use std::{
/// static COUNT: Atom<u32> = |_| 0; /// static COUNT: Atom<u32> = |_| 0;
/// ///
/// fn Example(cx: Scope) -> Element { /// fn Example(cx: Scope) -> Element {
/// let mut count = use_atom_state(cx, COUNT); /// let mut count = use_atom_state(cx, &COUNT);
/// ///
/// cx.render(rsx! { /// cx.render(rsx! {
/// div { /// div {