Why Rust
Should I write the whole app in Rust, or put a web or Swift interface on top of a Rust engine? Here is what I weighed and what decided it.
All Rust: 0 frames, 0 boundary crosses. Drag the slider or press Run.
What I learned
- Write down the trade-offs before you pick. The list showed me what I did not know yet.
- Pick a design that you can debug yourself. Sometimes an agent fixes one symptom after another and does not find the cause.
- Plan for the next one or two steps. Swift is good for a Mac-only app, but I wanted a browser version, and that ruled Swift out right away.
- Keep the core separate from the UI.
Why I considered Rust
I think that Rust is a very interesting language. It is roughly as powerful as C++, and its strict compiler refuses to build code that looks unsafe. It feels like a picky reviewer who sits next to me.
But that does not make Rust the right choice for a photo editor. I had many questions:
- Only the image engine in Rust, with a React UI?
- All of the app in Rust, if a Rust UI library is good enough?
- A Swift UI that feels like a native Mac app?
I also wanted a browser version one day, so that I can edit without my Mac. I might also want to support Windows and Linux one day.
The cost of each option
All Rust gives me one language. I do not copy image data back and forth between two programs, or pass each click and drag across a bridge between them. The app is faster, needs fewer external libraries, and is simpler to ship.
But Rust has no official UI toolkit. The community libraries work, but my UI will not look as good as a well-styled web page or a native Mac app. Browsers give good accessibility (support for screen readers and similar tools) by default. The Rust UI libraries are far behind.
A split design (for example, a React UI in Electron) lets me use the best tools on each side. With CSS, I control each pixel. The cost is two systems that I must keep in sync.
The Swift option
A Swift interface feels the most natural on a Mac. But it is also a split: Swift communicates with a Rust engine. Swift does not run in the browser, so a browser version (made with WebAssembly, which lets compiled code run in a web page) needs a second interface, written from scratch. Swift also ties me to Apple: Windows or Linux needs one more. The Rust UI libraries and Electron already run on all three.
Comparison
| All Rust | Rust + React | Rust + Swift | |
|---|---|---|---|
| Languages | 1 | 2 | 2 |
| Engine and UI to keep in sync | No | Yes | Yes |
| Extra runtime to ship | None | Electron | None |
| Appearance | Plain | Anything CSS can do | Native Mac |
| Accessibility | Limited | Browser-grade | Native Mac |
| Browser (WASM) version | Same code | Engine to WASM, UI as is | Needs a second UI |
| Windows and Linux | Same code | Same code | Needs another UI |
Why the handoff decided it
In a web app, people expect a short delay. A photo editor is different. Drag, zoom, and pan are continuous. Each mouse movement must reach the engine and come back as a new image, with no lag that you can feel.
To keep two systems in step at that rate is much more difficult than to handle an occasional request. When they get out of step, the bug usually sits in the handoff, where the code on each side looks correct on its own. So this type of bug is very difficult to track down.
Coding agents wrote most of this code. In my experience, they are strong when all of the code is in one language. They are much weaker when they must find a small mismatch between two systems in different languages. With one language and one program, there is no handoff where this bug can occur. When the agents get stuck, I can find the problem myself.
The second reason was performance. I want the app to be fast and to use only the memory that it needs. All Rust has no second runtime to load, and no copies of the image go back and forth across a runtime boundary.
How I keep the option to change
I keep the UI and the photo editing core in separate modules, so I can swap the interface later. If I want a different interface in a year, most of the core comes with me without changes. For the same reason, I clean up sloppy generated code when it arrives. I want the code to stay small enough that I can understand all of it.