LightPhotos

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

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.

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

One Rust app
UI module
Core engine

Option 2 · Split

Electron
React UI
Rust
Core engine
With all Rust, the interface calls the engine directly. With the split, every slider drag and every preview has to pass between two separate programs.

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.

ONE SLIDER DRAG, 60 TIMES A SECOND All Rust Slider moves Edit function New pixels No runtime boundary. It is a function call. Rust engine, web UI Slider moves (JS) Engine (Rust) New pixels (JS) Two boundary crossings per frame, 120 a second.
The Rust engine runs just as fast in the split version. The extra time comes from the data crossing between the two sides twice every frame.

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.