LightPhotos

Learning Rust while the agents write it

I picked Rust when I knew only the fundamentals.

FIVE TASKS. ONE OF THEM HAS A BUG. Task 1 built app runs Task 2 built app runs Task 3 suspect app breaks Task 4 waiting Task 5 waiting one build, one run: app breaks

Task 3 broke the app. App runs: 3. Suspects: 1.

Pick how the five tasks land, then press Run. One task at a time costs more runs, but it tells me which task broke the app.

I took on two problems at once

When I chose Rust for the whole app, I knew the basics. That is very far from image code that keeps up when you drag a slider. The second problem is the goal: the image must change as soon as you move a slider, even in a folder with thousands of RAW files. That is a speed target, and in my experience speed is very hard to add at the end. Many side projects stall when they take on both problems at once. I think that mine did not stall because I do not type the code.

Where my time goes

WHO DOES THE WORK, BY STAGE Plan me, and I do not hand this one over Build the agents write almost all of it Test me, in the real app Review me, reading the diff Release scripted, and safe to run twice BLUE IS THE WORK THAT I DO WITH MY OWN HANDS
The agents do nearly all of the typing. I keep the planning for myself, because everything after it depends on a correct plan.

Planning is the part that I do not hand over

Every change starts as a paragraph and a list that I write myself. It says what the change is, which parts of the app it touches, what I leave out on purpose, and how I will know that it worked.

That last item does most of the work. Without it, the agent picks its own idea of "done", and that idea is usually generous to itself. "Make thumbnails faster" is too vague to check. This target is not: "A folder of 3,000 photos fills the first two screens, the scroll does not stutter, and memory does not climb while I scroll."

A plan is also the cheapest place to be wrong. Sometimes an agent confidently builds the wrong thing. If I find that in a paragraph, it costs me a minute. In finished code, it costs an afternoon, and I probably will not throw that code away, because by then it compiles and I am attached to it. This is the habit of the seven-item list in the first post.

Building one thing at a time

Every change must leave the app in a working state. I build and run the app after each task. When two changes land together and the app misbehaves, I cannot tell which one caused it, and that is what I most need to know. Agents write a lot of reasonable-looking code very quickly, and if I let them bundle changes, I lose that clue.

I do not take the agent's word for it

Agents are quick to announce success: "The thumbnail loading should now be much faster." That word "should" cost me more time than any bug in this codebase. I do not count a number until I see it myself.

The pixel maths has real tests. Exposure, the tone curve, white balance, and the other adjustments are pure functions: the same input always gives the same output. So I check them against fixed numbers.

I check scrolling, decoding, panning, and memory when I run the app. This is my weakest area. I can see if something is broken, but not if a change made the app faster or slower, because I never click the same way twice. I want to fix that next.

Getting a second agent to review

When a change is done, a fresh agent with no memory of the conversation that produced the code reviews the diff, the list of lines that the change adds and removes. This helped more than I expected. The agent that just wrote four hundred lines is a poor judge of them, and it will happily agree with whatever I suggest.

WHO SHOULD REVIEW THE DIFF The agent that wrote it Agrees with you, warmly It just wrote four hundred lines. It is the worst possible judge of whether they are any good. A fresh agent, no memory A clone to avoid the borrow checker An allocation in a per-pixel loop An unwrap on user input A lock held during slow work A refactor that nobody asked for
The fresh reviewer finds the same kinds of problems again and again. Most of them are about wasted work. Few of them are real bugs.

Rust compiles all five without a warning. The compiler has a set of rules about who owns each piece of memory, and a clone (a copy) is the easiest way to make it stop complaining. On a 60 MB image in a function that runs every frame, that copy is a real problem. A request for new memory inside a loop that runs once per pixel looks reasonable until it runs twenty million times. A lock stops other threads from touching some data, so holding one during slow work is how a background task ends up freezing the interface.

Then I read the diff myself. I rarely catch what the reviewer missed, but reading diffs is how I learned Rust.

Keeping releases dull on purpose

A script bumps the version, builds each platform, and updates the site with the new build. It is safe to run twice by accident. If a release takes concentration, I put it off, and then nobody can use a month of work.

Stage What I hand over Done when
Plan A paragraph and a list I can state what would prove it
Build One task, with its scope It compiles and the app still runs
Test The proof from the plan I saw it, on a real folder
Review The diff, to an agent that did not write it Findings are fixed or consciously declined
Release A version number The build on the site is the build I tested

Four ways this goes wrong

Fixing symptoms. The agent patches one symptom, then the next one. After four rounds, the code is full of special-case checks, and nobody has said what the real problem is. Now I stop and reproduce the problem, and the agent names the cause before it changes any code. Then it makes one fix.

Confident numbers that nobody measured. An agent once told me that a change made something "roughly three times faster", when it had not run anything at all. If I do not see a number get measured, I treat it as a guess.

Code that is correct but wasteful. Rust's compiler checks that your code does nothing unsafe with memory. It does not care if you copied fifty megabytes for no reason. Speed is the point of this app, so a change must pass a speed check before I accept it.

The change keeps growing. A change picks up work that nobody asked for. The out-of-scope list in the plan stops most of it, and I catch the rest in the diff.

How I learned Rust

Four things taught me, in approximate order of value.

A few weeks in, I can read all of the Rust in LightPhotos and write most of it. Reading matters most. An agent can always write more code than I can, but it cannot tell me if that code is good. If I cannot tell good code from code that only looks right, I will get an app that only works, not the app that I want.