Does Dioxus spark joy?

Amos

Note: this article is adapted from a presentation I gave at a Rust Paris Meetup — that’s why it sounds a little different than usual. Enjoy!

Good evening! Tonight, I will attempt to answer the question: Does Dioxus spark joy? Or at the very least, whimsy.

What’s Dioxus, you ask? It is first and foremost a name that is quote: “legally not inspired by any Pokémon”.

The deoxys pokemon

Even if the author concedes in a Hacker News comment that the “Deoxys” Pokémon is, I quote: “awesome”.

To cover any upcoming legal fees just in case Nintendo doesn’t buy that origin story, Dioxus is, as of the summer of 2023, a YCombinator startup.

The YC page for dioxuslabs, active since summer 2023, team size 4, founder Jonathan Kelley.

Regulars might be wondering at this point, what does any of that have to do with Rust? Well, don’t worry, I have gone and checked for you: Dioxus is, in fact, getting money from Huawei, which makes it a Rust project just like any other.

Screenshot of the YC page where it says Dioxus Labs gets Huawei money.

Please find on this diagram, in red, every Rust project funded by Huawei, or any other kind of -wei.

Huawei funding diagram — the XKCD meme where everything relies on the work of one person, except it's colored in red mostly.

Dioxus is the promise of having a single code base for your mobile apps and web apps and desktop apps. It makes me think of React Native or PhoneGap. If you’ve heard of PhoneGap, remember to stretch. It’s very important at our ages.

Dioxus “fullstack” goes one step further, with a single code base for the client and for the server. But what does that mean? How did we get here? Let’s go back in time for a minute or twelve.

A short and mostly wrong history of web apps

There’s been plenty of different paradigms when it comes to web apps, and I won’t cover them all.

In short, in the first generation, “generating HTML” is the job of the server. You are allowed to sprinkle some JavaScript (or, god forbid, Visual Basic Script) to make some stuff move, but that’s as far as it goes.

The image is a diagram illustrating the relationship between a server and a client in web rendering.
	•	The left half is blue and labeled SERVER, containing an oval labeled “render.”
	•	The right half is white and labeled CLIENT, containing an oval labeled “display.”
	•	Between them, a box labeled HTML sits in the center, with an arrow from “render” to “HTML” and another arrow from “HTML” to “display.”
	•	The diagram shows that the server renders HTML, which is then sent to and displayed by the client.
	•	There is also a small orange crab illustration in the top-right corner.

Note that I’m saying “render” in that diagram for “generating HTML”, with my apologies to people who work on Servo and Skia and whatnot. I’m just reusing the React nomenclature, sowwy.

In the second generation of web apps, the world has now written a critical mass of JavaScript. We’re starting to have something that resembles DevTools. If you remember Firebug, that was among the first. And maybe you should work on a will or something like that.

The image is a diagram showing the relationship between a server and a client in a web application using data exchange formats.
	•	The left half is blue and labeled SERVER, containing an oval labeled “databases and co.”
	•	The right half is white and labeled CLIENT, containing two stacked ovals labeled “render” and “display,” connected by a downward arrow.
	•	In the center, a box labeled XML or JSON ou whatever connects the server and client with arrows pointing from left to right.
	•	The diagram illustrates that the server provides data (e.g., XML or JSON), and the client renders and displays it.
	•	There is also a small orange crab illustration in the top-right corner.

We’re starting to get comfortable with the idea of a real application living inside of the web browser, which renders HTML based on structured data coming from the server. And then follows a decade of developers using something called “XMLHttpRequest” to send JSON around. Oh well.

We’re starting to have web apps that work offline. However, the initial loading experience is bad. Visitors have to wait for the entire app to load, then they wait for the app to do its API calls, and then for the app to render the HTML, which can take a long time: planet earth is starting to develop a collective distaste for the spinner.

And that’s not the only problem we’ll have to address for SPAs (single-page apps). We’ll have accessibility, navigation history, search engine optimization, and of course, data loading. If every component on a page does its own API request to get some data and then render, that’s a lot of requests per page.

Especially if you looked at React the wrong way and your component is doing API calls in an infinite loop, then it’s a lot, a lot, a lot of API calls. And yes, that is actually what took Cloudflare down recently.

The image is a diagram showing how server-side rendering with client-side hydration works.
	•	The left half is blue and labeled SERVER, containing an oval labeled “render.”
	•	The right half is white and labeled CLIENT, containing two stacked ovals labeled “display” and “hydration,” connected by a downward arrow.
	•	In the center, a box labeled HTML + JSON hidden somewhere connects the server and client with arrows pointing from left to right.
	•	The diagram represents a process where the server renders HTML along with embedded JSON data, which the client uses to display the page and then hydrate it for interactivity.
	•	A small orange crab illustration appears in the top-right corner.

So boom, third generation, full stack, best of both worlds. We do the render on the server like before, and we stream it to the client, which can display it as it’s being received. But alongside that rendered HTML, the server also sends the structured data that it used to render the HTML.

A practical example

Here’s a practical example. It’s a counter written in Dioxus:

#[component] fn Counter() -> Element { let mut x = use_signal(|| 0_u64); let inc = move |_| x += 1; let dec = move |_| x -= 1; rsx! { "{x}" button { onclick: inc, "+" } button { onclick: dec, "-" } } }

When we do the server-side rendering, we just say, okay, there is a variable x that starts at zero. It’s used in the macro RSX, and then there’s two buttons.

The two buttons do something if you click on them, but can we do something about it on the server side? No, those event handlers have to be registered on the client side. All we can do is send hints.

Here’s the HTML markup sent by the server:

<!--node-id0 -->0<!--#--> <button data-node-hydration="1,click:1">+</button> <button data-node-hydration="2,click:1">+</button>

There’s no onclick attribute on the button tags directly. There’s only information that references the structured data (that I’m not showing you here to avoid a wall of text).

So the client has the same data the server had. It’s doing the same render as the server did and then it creates a mapping between what the server sent and what the client rendered. Then it takes over the document, installing event handlers, making everything interactive, a process we call hydration.

Now the whole point of having the server stream markup is that we can show it early before the app is even loaded on the client. But what happens if we click on a button in the middle of the hydration process?

In theory, the server markup could include actual links or forms that would trigger regular browser actions. But in practice, it’s been a while since I’ve seen anybody bother doing that.

Now, what happens if during hydration the client render doesn’t match the server render? Then it can’t create a mapping and everything’s broken. I think the best case you can do here is just replace everything with the version that the client rendered, which is still pretty bad as everything would jump around.

And now what if we need data that takes some time to fetch? For example, we’re fetching from a database or an API. That’s a family of problems that the industry has been trying to solve for years. And Dioxus offers several solutions in the form of hooks:

  • try_use_context
  • use_after_suspense_resolved
  • use_callback
  • use_context
  • use_context_provider
  • use_coroutine
  • use_coroutine_handle
  • use_effect
  • use_future
  • use_hook
  • use_hook_did_run
  • use_hook_with_cleanup
  • use_route
  • use_router
  • use_navigator
  • use_memo
  • use_memo
  • use_on_unmount
  • use_reactive
  • use_resource
  • use_root_context
  • use_set_compare
  • use_set_compare_equal
  • use_signal
  • use_signal_sync
  • use_reactive!
  • use_server_future
  • use_server_cached
  • use_drop
  • use_before_render
  • use_after_render

So there’s a little something for everyone. There’s synchronous hooks. There’s asynchronous hooks. There’s reactive hooks. There’s hooks that cache the results. There are hooks that only run on the server side or only on the client side.

It’s a little bit intimidating, to be honest. We’re far from the “if it compiles it works” that I got used to, in Rust?

If you break the rules of hooks, you don’t get a build error or even a runtime error. You just get a weird behavior, which can be hard to debug.

But there’s kind of a good reason for that.

It’s that full stack stuff is complicated. It truly is. It’s not that Dioxus added complexity where we didn’t need any. It’s that this is a problem that’s inherently complex.

Love-hate

Now… I have a confession to make.

I was originally planning to be a little rough on Dioxus because I challenged myself to use it to build the quizzing software that I used at Eurorust in Paris. And that was honestly a pretty frustrating experience.

But as I dug deeper, I realized that most of my complaints were really just misunderstandings, or already in the process of being fixed in the main branch, or simply limitations of the current WebAssembly/Rust ecosystem.

I was going to start with praising the developer experience of Dioxus with their dx tool, which wraps cargo and takes care of compiling WebAssembly. I was going to praise the loading screen you see in the browser while it’s compiling everything…

What dx serve looks like

But then I was going to complain about the fact that if there’s a panic in the app, it just becomes unresponsive! There is nothing that shows you that something went wrong and the entire app is broken!

Well, in the main branch, there is! Of course they added it! It makes sense, and they are smart people using their own stuff.

Next, I was going to complain that the stack traces are completely useless because all we see are function names with numbers and hexadecimal offsets into a big WASM file.

A stacktrace where every function name is $func

But since then, I’ve found this Chrome extension called C/C++ DevTools Support (DWARF), which looks exactly like what someone would come up with if they were trying to target me specifically with malware.

The C/C++ DevTools Support chrome extension page

And yet it works, it doesn’t actually give us the name of the functions but it does show the name of source files and lines. And if you click them they open up in DevTools, and you can place breakpoints, you can step in, step over, step out like a real debugger.

The stack traces now have rust source names and line numbers.

It’s honestly a lot better than I imagined. I didn’t even know we were so far with WASM debugging or that we had picked DWARF for that but I guess that makes sense.

The debugging experience in dev tools.

Next, I was going to complain about Subsecond, their hot patching thing.

So, what is hot patching? When you develop a web application, it’s kind of a given now because of React and Svelte and whatnot, that you should be able to modify source code that corresponds to a component and that When saving that file in your editor, the change should apply directly in your browser without changing the state of the application.

So if you’ve navigated deep into your application’s hierarchy, hot patching doesn’t reload you back to the start page. It doesn’t reload the page at all. Instead, it updates only the components that have changed on the current page.

And I thought I was using that with Dioxus and I thought, wow, it doesn’t really work well at all. It actually does lose state and it’s not actually under a second. It turns out I hadn’t enabled it. I forgot. You have to pass --hot-patch and I… didn’t know.

When I did enable it, I noticed that it worked really well. Like, well, it crashes all the time, because what it’s doing is a lot more complicated than the JavaScript framework, and it’s still early days. But the promise is here. You can make a change and see the result very quickly in your browser.

And you wanna know something funny? When you enable hotpatching, Stacktraces show the actual mangled name of Rust functions. But, it also breaks DWARF debugging, so uhhh.. your choice I guess.

Does Dioxus spark joy?

It’s time to answer the question, does Dioxus spark joy? I’m gonna say: not yet.

For now, without subsecond and everything, it’s still really unpleasant for the most part, compared to Svelte 5 which is my gold standard. But I can see what the Dioxus team is going for and I’m really excited for it.

I was kind of skeptical going into this. I was like: I’m gonna get Rust enums, which is great, but everything else is going to suck.

But I was wrong! The generational references make event handlers not actually miserable to write. Server-side functions with web sockets actually work pretty well. And get rid of a lot of boilerplate.

The Dioxus team is doing a lot of hard, interesting work. They have a Flexbox implementation that they’re sharing with Servo. They’re doing their own HTML and CSS renderer now to make desktop applications without a full-fledged web engine.

I’m very much looking forward for Dioxus and the entire WASM on the front-end ecosystem to catch up with the JavaScript-based solutions in terms of developer ergonomics.

In the meantime, I’ll be doing Rust on the backend, and TypeScript on the frontend.

Afterword

So since I gave this talk, I’ve written several thousand lines of dioxus for an upcoming project, which… made the conclusion a lie I guess. I still feel conflicted about it, but I guess I’m also invested in it now. Let’s… see where this goes!

(JavaScript is required to see this. Or maybe my stuff broke)

Did you know I also make videos? Check them out on PeerTube and also YouTube!

Here's another article just for you:

Remote development with Rust on fly.io

Disclaimer:

At the time of this writing, I benefit from the fly.io “Employee Free Tier”. I don’t pay for side projects hosted there “within reasonable limits”. The project discussed here qualifies for that.

Why you might want a remote dev environment

Fearmongering aside — and Cthulhu knows there’s been a bunch, since this unfortunate tweet — there’s a bunch of reasons to want a remote dev environment.