GUIs with front-end decoupled from back-end

The question

  • I want to write some cross-platform applications in Rust.

  • These applications need GUIs.

  • I don't want to commit to any single GUI crate / framework / technology, but want to be able to explore alternatives, so I want the business logic to be decoupled from the GUI logic.

  • Mention of JavaScript and mainstream web-app development practices makes me break out in a cold sweat.

Can you recommend an approach to writing GUI-enabled Rust applications with business logic decoupled from GUI code?

Background Rant

Back in the last millennium (1999) I wrote a web-based visualizer for 3D Computational Fluid Dynamics data, in JavaScript, directly fiddling around with the DOM.

I hated everything about this experience (except for the final result, which looked pretty and made a bunch of people happy), as far as I can recall for 2 major reasons:

  1. JavaScript: a language that made C++ look sane by comparison.
  2. The first browser wars: every problem needed to be solved at least twice, in incompatible ways, in the same code base.

Doing battle with C++ involved sufficient intellectual challenges to make the pain bearable, but the JS + DOM experience was pure soul-crushing torture which made me determined to give JavaScript and web applications a veeeery wide berth. So imagine my growing sense of unease and disgust over the next decades as JavaScript and web apps took over mainstream programming and, to a large extent, became a core activity for the majority of software developers. It is as if the world has been taken over by zombies, and I spend my life hiding in zombie-free enclaves.

I have mostly avoided the need to write GUIs in my Real Work, but I have an increasing number of side-projects which don't make sense without them. I would like my GUIs to be

  • cross-platform
  • written in Rust
  • have the business logic decoupled from the UI.

Imagine my horror, especially as a physicist, at discovering that Electron

  • nowadays means: Build cross-platform desktop apps with ... JavaScript, HTML and CSS
  • is the de-facto standard way of building cross-platform apps.

Down the years, various words such as React, Vue and Angular penetrated into my consciousness. I don't really know what they mean, but I do associate them with the zombie apocalypse so they trigger angst. Looking at the various options for building GUIs in Rust (and elsewhere), I see that

  • A large proportion of the documentation assumes that you are a zombie: familiarity with React/Vue/etc. is taken for granted, or similarity to them is touted as a major advantage of the solution.

  • The business logic tends to be deeply entangled with the UI.

    For example, is it possible to write an app using Dioxus for the GUI, without fully committing to expressing your business logic in Dioxus itself? (Repeat the question with Dioxus replaced with just about any other framework. Iced and the Elm architecture seem the least guilty here.)

Please help me get over my deep-seated dread of JS-related stuff so that I can write some GUIufl apps in Rust. Is it possible to write the business logic core of such apps in a GUI-agnostic way?

1 Like

Skipped the rant, but did you have a look at Tauri? It specifically states that it is frontend-independent (I've used it only with next.js so far). It does require web frontends though, as it basically renders the GUI in a webkit browser. Not exactly sure if this is a no-go for you (I don't know how to interpret the JS making you break out in cold sweat requirement). Exciting Rust-based frontends you could use with Tauri are Yew and Leptos. More akin to a fully integrated solution—contrary to Tauri—is Dioxus (albeit the only Rust framework supporting mobile targets as far as I'm aware).

1 Like

You only mention web GUIs - is it a web app you are building?

React has been massively influential and it helps to be
familiar with it if you use, say yew or one of the other frameworks that take inspiration from it.

Also - what platforms? If it includes mobile, then maybe look at flutter also.

FWIW, JavaScript is a completely different beast than the ES3 you would have been using then, and the browser API is a lot more consistent, reliable, and powerful too. It's the biggest turnaround I've seen in terms of how I feel about a language, to the point that it's gone from slightly better than Visual Basic for Applications (still the worst language I've used) to my current preferred scripting language.

There's still plenty of quirks happy to jump out of the grass, and while the tools themselves are actually pretty good, the experience is fairly exhausting. Even just counting popular, current tools for purely JavaScript, no browser stuff, you have: node, deno, bun for runtimes, npm, yarn or pnpm for package managers, nvm, volta for version managers, prettier for formatting, eslint for linting, mocha, jest, vitest for testing frameworks, rollup, esbuild, swc for bundlers, and of course typescript; and all of these tools have their own ecosystems of configuration management and plugins, some of those with their own ecosystems again..., and none of them talk to the configuration of anything else, and all of them have different support for different versions of module resolution and package management.

But still: you don't need any of that stuff really, and you can do some really powerful stuff with remarkably nice code now (just don't expect anywhere near Rust performance without some contortions - while it can happen, it's not the common case.)

As such, while there are lots of nice GUI libraries for Rust (check out the exhaustive list on https://areweguiyet.com/ if nobody beat me to it) most of the pure Rust ones are not what I'd call primetime yet; (iced is probably the closest to it still, but it's been a long time since I've checked), and Tauri absolutely is by the measure of piggybacking on the browser as a platform, and having really nice binding between the browser frontend and the Rust backend.

With respect to browser programming; I would say don't start with a rendering framework like React or Vue just yet until you have a better grip on the current DOM and CSS APIs to see what they're building on and providing for you, then you can make a more informed decision, maybe just throw a light CSS reset in just to get started. That gets you remarkably far nowadays, far further than some native GUI environments - there's only some rough patches like trying to build a decent drop-down/combo list from scratch that aren't quite ready (brand new APIs like popover and anchor positioning close up most of those issues though). Native custom components are in theory ready to use, but there's enough mismatch with the existing frameworks that they haven't really taken off yet, so that's where you might make a decision there.

3 Likes

Given how prominently JS-related keywords appear on anything Tauri-related, I have avoided it.

Perhaps I should find the energy to give it a deeper look. In a 3-minute attempt just now, all I see (apart the statement that it is implemented in Rust) is JS and its ecosystem.

I'll try again when I have more time.

Briefly (as described in the rant) a nasty experience developing a web app in JS has made me avoid all JS-related things like the plague, for a quarter of a century.

I am not sure whether at this stage I can (or want to) get over this fear of anything related to JS. I'm sure that my JS fears are irrational to some extent, but I do also believe that there are some very serious problems with JS &c. Briefly, the things that made me hate JS are pretty much diametrically opposed to the sorts of things that make me love Rust.

I was hoping that Rust + Wasm would eventually allow me to build cross-platform GUI apps while pretending that the whole JS-taking-over-the-world thing never happened.

Dioxus is a prime example of something that makes me wonder how to decouple the buisiness logic from the UI.

Makepad supports mobile too.

Also, I'm not really sure what to make of Robius

Part of the problem is that there is just so much of this stuff.

Sorry, I hoped that "cross-platform" would have made it clear that I am not exclusively interested in web apps. But perhaps my understanding of this term is heterodox.

I have a bunch of small nascent projects for which I would need GUIs, some of them not intended for web at all, some primarily for the web, and at least one in which desktop, web and mobile would probably be required.

Just to ease some of the old worries, "modern" web is much nicer than the turn-of-the-millenium web. Browsers actually agree on the well established parts and JavaScript has improved in many ways. Typescript improves it further if you like static types, but introduces a build step. You will find all sorts of recommendations for toolsets and stacks, but it's still possible to use a minimal setup. Browsers are still compatible with the 90's, but there was suddenly more incentive to make them nicer to develop for.

1 Like

Indeed, and I don't have the time and energy for this, especially as these are essentially spare-time projects.

This is a huge benefit of using Rust: I have to spend exactly 0 effort on deciding which package and dependency management tools to use.

And if someone were paying me to use it, then this would make me happy. But these are spare-time projects, in which I have exactly zero motivation to give a hoot about JS, old or new.

Maybe this is a mistake, maybe I'd love modern JS (or TS), maybe I'd make more progress if I got over my JS fear, but that's how it is.

Been there, done that ... many times. The problem is that there is too much of this stuff. Which brings me back to the key point of my question, which seems to have been lost: how can I decouple the business logic from the GUI so that I am not tied to a single GUI?

Let's take the example of Dioxus (which was mentioned upthread, and which you and I have discussed before, methinks). It looks like the natural way to manage your business data in Dioxus is to store it in Dioxus signals. This tightly couples my business logic to the GUI-as-expressed-in-Dioxus, and if I want to try another GUI framework, I have to rewrite my business logic from scratch.

Please tell me it ain't so! Please tell me that I have misunderstood. Please tell me how to do it better.

I'm not particularly interested in browser programming and definitely do not want to start with React or Vue, it's just that many of the landing pages of cross-platform GUI solutions in the Rust ecosystem proudly announce something like

This is inspired by React (or Vue, or whatever) so you'll feel right at home.

Nooooo! I do not feel at home with the zombies FFS! Run away! Run awaaaaaay!

Let me emphasize the key point of my question, lest it get lost again: Is it possile to keep my business logic decoupled from whatever GUI framework I happen to be trying out today, so that I can try a different one tomorrow without having to rewrite my business logic from scratch every time?

Well, you can still split your project into modules or crates to get separation between your UI and your business logic.

Thanks for sharing Makepad, I was not aware of the project.

Thank you for trying to ease my worries :slight_smile: It hadn't escaped my attention that we have come out of the browser wars and reached a much better place, and that JS (and TS) are also better.

The thing is, I don't want to become a web dev, I just want to write some apps in my spare time and I want to do that in Rust, and I want those apps to have GUIs, and I don't want to be tied to any specific choice of GUI framework, so I am looking for a strategy that lets me try different (Rusty) GUIs for manipulating my business logic.

Totally fair and I'm not trying to convince you to use it. :slightly_smiling_face: It's of course not everyone's primary option.

In terms of strategy to separate gui and business logic. Rust works well with message passing to share data between processes and enums make matching "events" natural.

So I'd suggest a client-server architecture.

That doesn't require html or JavaScript at all of course.

You could use REST and a web server framework for the server, or it could use an RPC library like tonic or just listen on TCP using tokio.

1 Like

With pleasure I read the Rant(s). I could have written it myself...
It made me curious what the 'backend' projects are. What kind of stuff is happening there?

P.S. many times I seriously contemplated making a complete GUI myself, independent of (almost) everything.

Maybe check out iced? It's based on message passing, meaning you can keep your data and business logic outside the UI stuff and only update in response to some message.

You'll have this problem with any framework that implements fine-grained reactivity, as it will want to look into your data to understand the dependencies between it and the UI and thus determine what to update and when.

2 Likes

What kind of application and what kind of GUI do you have in mind?

The YouTuber Tsoding shows how he created a polished GUI in C using the Raylib graphics library. Complete buttons, sliders, accelerated graphics and shaders You don't need React if you have C - Native UI is the future.. There are Rust crates for using Raylib available so in a couple of days you could have quite a nice GUI.

I had the misfortune to have to do web development for a year in 2000 or so. It was so "icky" I swore never to go near it again.

Six years or so ago I had been working on GUI stuff with C++ and Qt. This involved receiving data streamed from a server in the cloud and displaying the data in 3D in real time. There were maps and charts involved as well. One day our boss said "Wouldn't it be great if we had that in a web page". My heart sank. Luckily they contracted a FLASH developer to get a demo working. We had six weeks to get it working for showing at an industry expo.

I had heard of some new web standards, like web sockets that would make streaming data into a web page easy and webgl that would make 3D graphics easy and a library THREE.js that made 3D stuff even easier. So I set about learning JavaScript and the DOM API's and the those new features. Long story short was that I got our required web page working just in time for the expo demo. Meanwhile the FLASH contractor had failed to complete.

That left me in the odd situation that I love Rust for it's type checking, lifetime checking and general elegance and I love Javascript as the polar opposite.

I started to go down the rabbit hole of React and such. It's a nightmare. The mere idea that an interpreted language needs a build step is just wrong.

So there we have it, write your application in Rust. Do the GUI stuff in a web page. Hook them up with web socket.

Various, including

  • Something to facilitate live data/statistics gathering during matches in an little-known sport.

  • Something to help make class lists with photographs and matching names, by running face detection on group photographs, allowing manual adjusting of the cropping of the detected faces, associating names, producing PDFs, and saving the state for tweaking in the future.

    Although it was never my intention, there is now talk of this being used by many other people, in which case there may well be a hard restriction that none of the data should ever leak from the user's device. Most of the people to whom this would be extended cannot be expected to install any software on their device. Most of them won't possess any device beyond a mobile phone.

  • A frontend for normal people to interact with the beancount-based accounts I have for a small club/society. Not at all sure what I would want here, but something more much more 'normal' than Fava. I'll probably have to migrate away from beancount before eventually handing over to the next person: I'm orders of magnitude more productive in text-based formats with my highly-configured Emacs; normal people can only be expected to point and click.

  • A web-based application form for the aforementioned society, incorporating slightly intricate rules about multi-member families and specification of various, sometimes mutually-exclusive, options.

  • etc.

In short, a bunch of different things in which I would like to explore different approaches to UI design.

Yup, as I mentioned somewhere upthread, Iced looks the least intrusive of the things I looked at.

Thank you. I think that articulates why what I want cannot be done.

It's less "cannot be done", than "kind of a lot of effort", it's absolutely possible as you need to do it with a traditionally client/server web app for example, it's just a lot of messy glue that needs to exist to bind the two accurately and performantly.

It's generally not worth it for client side applications to completely separate their logic, but at least having a clean enough API to write tests without having to drill through UI is handy, and you can do that without too much trouble most of the time. In JavaScript land, splitting that difference has had a bunch of different attempts too, they just get less attention than the view side, but the biggest is probably still Redux, which is basically a "big ball of immutable state that you apply defined updates to", where you write a bunch of your behavior tests around just that very pure API without any UI types involved, but the trade-off is needing fiddly things like connecting components and selectors to get the fine-grained state back out, and it's tricky to avoid getting a bunch of UI state mixed in there.

Signals may or may not end up being found appropriate for application state, I don't know if we've had enough experience with them still.

1 Like
1 Like

Maybe checkout crux?
Right now the crux docs have examples with leptos for the web.
Maybe in the future crux will add slint or rui or makepad or even bevy.

  • Shared Core for Behavior - Crux helps you share your app's business logic and behavior across mobile (iOS/Android) and web — as a single reusable core built with Rust.
  • Thin Shell for UI - Crux recognizes that the best experiences are built with modern declarative frameworks such as [SwiftUI, [Jetpack Compose, [React/[Vue or a WebAssembly based framework (like [Yew — however, it aims to keep this UI layer as thin as it can be, with all other work done by the shared core.
  • Type Generation - the interface with the core has static type checking across languages — types and serialization code are generated for Swift, Kotlin and TypeScript. Rust shells can import the core directly.
  • Capabilities - capabilities express the intent for side effects such as calling an API. Because all side effects (including UI) are performed by the shell, the core becomes trivial to test comprehensively — test suites run in milliseconds (not in minutes or hours).
1 Like