Why Rust
Should I write the whole app in Rust, or put a web or Swift interface on top of a Rust engine? Here's what I weighed and what finally decided it.
What I learned
- Write the trade-offs down before you pick. Listing what each option gains and loses showed me what I didn't know yet, so I could answer those questions first instead of getting caught out by them later.
- Pick a design you can debug yourself. Agents write nearly all of this code and they're usually good at it. When one gets stuck fixing symptom after symptom, I need the design to be simple enough that I can step in and find the problem.
- Plan for the next step or two. Swift would have been a fine choice for a Mac-only app. Wanting a browser version ruled it out right away.
- Keep the core away from the UI. Even with one language, I keep the photo editing logic in its own module, so I could swap the interface later.
Why Rust was on the table
I find Rust a really interesting language to work in. It's roughly as powerful as C++, and its compiler is strict. It refuses to build code that looks unsafe, which feels a bit like having a picky reviewer sitting next to me.
That doesn't automatically make it right for a photo editor, though. Before I wrote much code, I had a pile of questions I couldn't answer.
- Is Rust a sensible choice for this at all?
- Should I write only the critical piece, the image engine, in Rust and build the UI with something like React?
- Or should I build the whole thing in Rust?
- Does Rust even have a UI library worth using?
- Or should I write the UI in Swift so it feels like a proper Mac app?
Two more things were nagging at me. I eventually wanted a browser version, so I could edit the same way without my own Mac. I also had a feeling I might want to support Windows and Linux one day.
In the end it came down to two ways of putting the app together.
Option 1 · All Rust
Option 2 · Split
What each one would cost me
Going all Rust, I'd have one language for the whole app. I wouldn't need to copy image data back and forth between two programs, or pass every click and drag across a bridge between them. It would also run faster, need fewer outside libraries, and be simpler to ship.
The downsides are real too. Rust has no official UI toolkit. The libraries people have built work, but nothing I make with them will look as polished as a well-styled web page or a native Mac app. Browsers also give you good accessibility out of the box, and the Rust UI libraries are a long way behind.
Splitting it would let me use the best tools on each side, and I'd have full control over the look, since CSS lets me adjust every pixel. The cost is keeping two systems in sync.
What about Swift?
A Swift interface would feel the most at home on a Mac, and I did want that. But it has the same split as option two, with Swift talking to a Rust engine, and Swift doesn't come with me to the browser. A browser version (built with WebAssembly, which lets compiled code run in a web page) would need a second interface written from scratch.
It would also tie me to Apple. Windows or Linux would need yet another interface, while the Rust UI libraries and Electron both already run on all three.
Side by side
| 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 |
| Look | 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 |
In a web app, people expect a short delay. You click a button, wait a moment, and the server answers. A photo editor is different, because dragging, zooming and panning are continuous. Every mouse movement has to reach the engine and come back as a new image, sixty times a second, without any lag you can feel.
Keeping two systems in agreement at that rate is much harder than handling the odd request. When they get out of step, the bug usually sits in the handoff between them, where neither side's code looks wrong on its own. That kind of bug is really hard to track down.
Why that mattered more than usual for me
This is the part that made the decision for me.
I didn't write most of this code. Coding agents did. In my experience they're strong when everything is in one language, and much weaker at spotting a subtle mismatch between two systems written in different languages.
I wanted a design where I can step in and find the problem myself when the agents get stuck. With one language and one program, there's no handoff between systems for that kind of bug to live in.
The second reason was plain performance. I want this app to be fast and to use no more memory than it needs, and with everything in Rust there's no second runtime to load and no copies of the image handed back and forth.
Leaving myself a way out
Even with one language, I keep the UI logic and the core photo editing in separate modules. If I change my mind in a year and want a different interface, most of the core comes with me untouched.
I also clean up sloppy generated code as it arrives instead of letting it pile up, for the same reason. I want the code to stay small enough that I can still hold it in my head.