Learning Rust while the agents write it
I picked Rust knowing only the fundamentals.
What I learned
- Write down how you'll know it worked. If I don't, the agent picks its own idea of done, and it's usually generous with itself. Then I build one change at a time, so when something breaks I know which change did it.
- Check the result yourself. When an agent says something "should now be faster", I don't count it until I've seen the number myself.
- Get a second agent to review the code. The agent that wrote it is a poor judge of it, and it tends to agree with whatever I say.
- Learn the language anyway. I don't need to type Rust faster. I need to be able to tell good code from code that only looks right.
I took on two problems at once
A few days ago I wrote about choosing Rust for the whole app. What I skipped in that post is how much Rust I actually knew when I chose it, which was the basics. I'd shipped Rust before, so I wasn't starting from zero. But knowing the basics is a long way from writing image code that keeps up as you drag a slider, and I knew that gap was going to hurt.
The second problem is the goal itself. I want a photo editor where you move a slider and the image has already changed, even in a folder with thousands of RAW files in it. That's a speed target, and in my experience speed is very hard to add at the end.
Taking on both at once is how a lot of side projects stall. I think the reason mine hasn't is that I'm not the one doing the typing.
Where my time actually goes
Planning is the part I won't hand over
Every change starts as a short written plan, and I write it myself. It's usually just a paragraph and a list. It says what the change is, which parts of the app it touches, what I'm deliberately leaving out, and how I'll know it worked.
That last item does most of the work. If I can't say what would show the change is done, I don't really have a plan yet. "Make thumbnails faster" is too vague to check. "A folder of 3,000 photos fills the first two screens without the scroll stuttering, and memory doesn't climb while I scroll" is something I can actually test.
Planning first is also the cheapest place to be wrong. An agent will sometimes confidently build the wrong thing. Catching that in a paragraph costs me a minute. Catching it in finished code costs an afternoon, and honestly I probably won't throw that code away, because by then it compiles and I'm attached to it.
This is the same habit as the seven-item list in the first post. I've found that writing the list up front matters even more when something can build the whole list in twenty minutes.
Building one thing at a time
The rule I keep coming back to is that every change has to leave the app in a working state. One task, then build, then run the app, then the next task. I don't let five tasks pile up and hope the build works at the end.
I do this because when two changes land together and the app misbehaves, I can't tell which one caused it, and that's the thing I most need to know. Agents can write a lot of reasonable-looking code very quickly, so if I'm not careful they bundle changes together and I lose that clue.
I don't take the agent's word for it
Agents are quick to announce success: "The thumbnail loading should now be much faster." I've lost more time to that word "should" than to any bug in this codebase.
The pixel maths is the easy part to test, so it has real tests. Exposure, the tone curve, white balance and the rest are pure functions, meaning the same input always gives the same output, so I can check them against fixed numbers.
Everything else I check by running the app myself, and this is where I'm weakest right now: scrolling, decoding, panning, memory. Clicking around a window tells me whether something is broken. It doesn't reliably tell me whether a change made things faster or slower, because I never click the same way twice.
I don't have a good answer for that yet. It's the next thing I want to fix, and I expect it'll be its own post. For now my rule is that something works when I've run it and seen it work, whatever the agent says.
Getting a second agent to review
When a change is done, a fresh agent reviews it. It has no memory of the conversation that produced the code, and I ask it to find what's wrong with the diff, which is the list of lines 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'll happily agree with whatever I suggest.
Those five come up over and over. Rust's compiler has a set of rules about who owns each piece of memory, and making a copy (a "clone") is the easiest way to get it to stop complaining. On a 60 MB image in a function that runs every frame, that copy is a real problem, and it still compiles without a warning. Asking for new memory inside a loop that runs once per pixel looks reasonable until it runs twenty million times. And holding a lock, which stops other threads from touching some data, while doing slow work is how a background task ends up freezing the interface.
Then I read the diff myself. I'm mostly not going to catch what the reviewer missed. I do it because reading those diffs is how I ended up learning Rust.
Keeping releases dull on purpose
A release is a version bump, a build for each platform, and the site updated with the new build. It's scripted, and it's safe to run twice by accident. I care about keeping it that easy. If a release takes concentration, I'll put it off, and then I'm sitting on a month of work nobody can use.
| 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 didn't 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
I don't want to make this sound smoother than it is. Here's where it goes wrong for me.
Fixing symptoms. One symptom gets fixed, another appears, and the agent cheerfully patches that one too. Four rounds in, the code is full of special-case checks and nobody has said out loud what's actually wrong.
Confident numbers that were never measured. An agent once told me a change made something "roughly three times faster" when it hadn't run anything at all. If I didn't see a number get measured, I treat it as a guess.
Code that is correct and wasteful. Rust's compiler is strict, and that made me more confident than I should have been. It checks that your code isn't doing anything unsafe with memory. It doesn't care at all whether you just copied fifty megabytes for no reason. The whole point of this app is speed, so I have to watch for that myself, and it's why a change has to pass a speed check before I accept it.
The change keeps growing. Left alone, a change picks up extra work nobody asked for. Writing down what's out of scope in the plan stops most of it, and reading the diff catches the rest.
So how did I actually learn Rust?
I didn't learn it from a book or by writing an app from scratch. Four things did it, roughly in order of how much they taught me.
- Reading diffs and asking why. Why is this line here? I asked that a few hundred times, about real code doing something I care about.
- Asking the agent to explain its choices. Why a
clone here, why a reference there, why
Arc(a way to share one value between threads) and not a plain value. Those answers are where Rust's ownership rules stopped being something I'd read about and became something I could predict. - Compiler errors. Rust's borrow checker, the part of the compiler that enforces those ownership rules, rejects mistakes bluntly and tells you exactly where they are. That taught me a lot.
- Writing some of it by hand anyway. One function per session, with no agent. It's slow, and it was the only way I found out what I actually understood and what I only recognised.
A few weeks in, I can read all of the Rust in LightPhotos and write most of it. Reading is the part that matters most for the way I work.
An agent can always produce more code than I can write. What it can't do is tell me whether that code is any good. I have to be able to judge that myself if I want the app I'm trying to build, and not just one that works.