Recently, I read an article on Habr titled «Go made boring — and after neural networks, it took off». In it, Go's intentional simplicity is presented as a successful solution that also proved useful when working with neural networks. And I started thinking: some features that Go doesn't have, I actually really like in Rust.
I'm willing to spend time learning the language if it makes understanding the code easier and changing it more comfortable. With Rust, it turned out exactly that way, although our initial acquaintance was quite painful.
Now the code is written by an agent. I read its changes, ask to rework solutions, and then return to this code. Therefore, I still care about which language it's written in.
A short 30-second historical overview
I came to Rust from Python, which I used while studying ML. Then came algorithms, and everyone was writing loops with conditions inside, and then more loops. At that point, I already knew another approach: my friend is a JS developer, and they constantly used map, filter, reduce. I liked that style, so I also tried to write in Python that way. Only later did I find out that this is actually called functional programming.

Then I wanted to study a language where I could dive deeper into FP and also find a job. I didn't want to learn something completely in a vacuum, so I looked at both languages and job postings. In the end, I chose between Scala and Rust. I initially chose Scala: I saw much more job postings for it, and they were closer to what I had already studied. Data Engineering, Spark, Big Data. I even though I don't smoke, I got a hit on Scala. I couldn't manage it at that time. Then I started switching to Rust. And here, the borrow checker almost everywhere stopped me.
By that time, ChatGPT was already around, and I fed it compiler errors. Clones appeared in the code, then more clones, and in the end, everything somehow worked. Why it worked and why it didn't compile before, I wasn't always sure. So I was a vibecoder even before the term existed.
Then I started figuring out how ownership and borrowing work, what the borrow checker wants from me. When I understood the logic of its requirements, working with Rust became clearer, though not always more pleasant.
What exactly did we simplify
I get annoyed when the absence of a certain feature in Go is presented as an advantage in itself. I think sometimes people confuse simplicity with lack of language features.
Rob Pike, one of the Go authors, explains in his 2015 talk "Simplicity is Complicated" that features complicate the language and make it harder to read code. But there he defines simplicity as the art of hiding complexity and gives the garbage collector as an example. That kind of simplicity makes sense to me: the runtime handles the complex work for the programmer.
The absence of a needed feature in the language doesn't make the task disappear. And it's not certain that solving it with what was left will be pleasant. Figuring out an additional language construct might be harder at first. But then you can use it in different projects, instead of writing and reading code that replaces it every time.
Incidentally, in the Go team's 2025 survey, 28% of respondents named the absence of a needed feature from another language as one of the main annoyances. In the open-ended responses, they discussed error handling, enums with data, nil safety, and verbose code. Familiar list.
For example, in the post about error handling, the Go team shows a function where four out of ten lines do real work, and the remaining six look like noise. At the same time, the team explains why they decided to keep the existing syntax. Often, you need to add context to an error, and it's convenient to set a breakpoint on a separate line with an if statement, while the IDE automatically adds repetitive checks, including using LLM.
When the code is written by an agent, you can compare how many tokens it takes to perform the same task in different languages. For example, in the experiment with MiniGit, the agent wrote a simplified Git in different languages and then added new commands. Opus 4.6 on Rust used about 14% more output tokens than on Go. With GPT-5.4, it's 11%. The difference with Python was more noticeable. It's a small task, but there was no cosmic gap between Go and Rust here.
If these tokens are costly, one can also look at Chinese models. For example, GLM-5.3 is quite comparable to Opus 5 in specific code benchmarks. A million output tokens cost $4.4 for GLM-5.3 and $25 for Opus 5. How many attempts will be needed for a specific project is already a separate question.
Personally, such a difference between Go and Rust doesn't convince me to abandon the convenient features of the language I'm used to. The agent writes the code for me. But I still read through it during code review. And every time I look at a function with 30 lines, seeing 15 lines of "if err != nil { return err }" personally irritates me a lot.
What I liked about Rust
I liked the combination of familiar ideas and convenient defaults in Rust. It was developed based on the experience of other languages.
Variables are immutable by default. Mutability is needed — explicitly write mut.
Normal enums with data and exhaustive match. If you enumerate variants explicitly, without a common branch for the rest, the compiler will show where the added variant needs to be handled.
A normal reference in Rust cannot be null. For a value that may not exist, there's Option
Result and ?, to combine error operations without repetitive if checks after each call.
Iterators come in all flavors: from classical map, filter, reduce to exotic ones like map_while, rposition, and try_rfold. Moreover, iterators are zero-cost abstractions: the compiler often converts them into regular loop code. What else is needed for happiness?
Error messages are also appreciated: the compiler shows where the problem is, explains what's wrong, and often even suggests a fix. You can feed the diagnostics directly into JSON for the agent via cargo check --message-format=json: with error locations and hints. There's even a meme about this topic.

The safe Rust rules exclude use-after-free, double free, and data races. This implies correct unsafe inside libraries; their authors are responsible for it.
Special thanks to Cargo. Dependencies, building, and testing are all in one tool. Add to this execution speed and the ability to get a ready binary without any JVM or other stuff.

What didn't like
Rust still has a steep learning curve. You need to understand ownership and borrowing, and habits from another language don't always fit.
Macros also need to be studied separately. AI generates them nicely, but then you still need to understand what it wrote and preferably test it.
When you're just learning, the borrow checker does the job effectively and prevents you from messing up with ownership and borrowing. But later you already know what you're doing, and it doesn't know about it. Sometimes the code is safe, but the borrow checker can't prove it, and you have to find workarounds.

Some people complain about the syntax. For me, it's generally okay, although lifetimes ('a, 'static) and turbofish (::
Slow compilation. Rust can take a long time to compile, but they're working on it. There's a comparison on Habr of building a project with different Rust versions: the same commit of hyperqueue compiled from scratch in 26.1 seconds on Rust 1.61, and 14.7 on 1.87. You can also tweak the settings yourself.
A large number of dependencies. You include one library, and it pulls in dozens, or even hundreds of dependencies. You need just one function, but you have to compile all this stuff. This is partially solved by feature flags. But if the library author didn't provide a way to disable unnecessary parts, then you have to include the whole set.
And, of course, async. In Go, you can run a regular function through go, and then the built-in runtime takes care of it. In Rust, you have to separately deal with function boxing and synchronization between synchronous and asynchronous code. And if you dive into the future mechanism, there's also Pin and self-referential structures. It's clear that these solutions have their reasons, but figuring out all of this isn't any easier.
When the agent writes code
When I started writing this article, I wanted to open it with a nice story. The agent made a mistake, the Rust compiler gave him a slap on the behind with a whip, and my future multi-million B2B AI SaaS was saved.
And here, I wanted to recall such a case andiiii...
While writing the article, such an error finally occurred after a very long time.
In one of my projects, the agent declared an array of HTTP headers via const. It seems he didn't notice that further in the code, a reference to this array was used in an asynchronous call inside tokio::join!.
The declaration itself was valid. However, const defines a value that is substituted upon usage. When borrowing this array, a temporary value was created. Inside the expanded join!, it would be destroyed before the future that needs these headers completes.
Here Rust gave the agent a slap on the behind: E0716: temporary value dropped while borrowed. The agent replaced const with static, and the code compiled.
The borrow checker doesn't know that the code was written by AI. Therefore, it treats it the same way as all the others.
Now the agent more often stumbles over linters and hooks. Fixed the code, ran the checks, fixed it again. And sometimes the agent comes to me himself: "Hey, leather buddy, there's a bug in my code, let me fix it."
To meet its requirements, one has to build skills and patch the harness. And yet, sometimes I open the code and explain the agent again what I've already written in the skill.
Indeed, the agent can also draw conclusions.
Remove the witnesses
When Rust hinders completing the task, the agent has two options. You can understand the cause and fix the solution. Or you can remove the obstacle. Sometimes the agent chooses the second option.
So .clone() appears after each note from the borrow checker, unwrap() and expect() instead of error handling, Arc
I got acquainted with clones even when studying Rust. If a separate copy of data is made for the solution, the compiler checks ownership of this copy. It won't check whether we even need it or if we're okay with the time and memory costs of copying.
With unwrap(), the situation is similar: the program explicitly allows panic on error. From the perspective of language rules, this is acceptable behavior. Whether we wanted to end the processing exactly this way, the compiler won't decide.

The consequences of unwrap() are reminiscent of the incident analysis of Cloudflare's failure on November 18, 2025. The Rust proxy FL2 received a configuration with a number of features above the limit. Calling unwrap() on an error led to a panic and 5xx responses.
The main thing that Rust doesn't check is logic. More precisely, its absence. Sometimes with the agent, sometimes with the person who set the task.
All these tools have normal uses. But if the agent takes them only for a green exit code, I want to see what exactly he changed.
You can write in AGENTS.md in caps: "NEVER USE UNWRAP!!!" The agent will read, agree, and after twenty minutes write unwrap(). The build will proceed. If such a call is prohibited in the project, the rule can be enforced using Clippy's unwrap_used and expect_used lints. Then, with agreed-upon settings, the check will detect a violation, even if the agent forgot the instruction text.
To complete the task, there remain cargo clippy -- -D warnings and cargo test with agreed-upon project settings. But the agent may change not only the types in which we recorded the requirement but also the check configuration. Such changes also need to be reviewed: after relaxing the requirement, the previous error may no longer be detected.
Therefore, lint changes, new #[allow] and weakening existing tests require separate attention during the review. A deleted test does not fail, and the agent knows this perfectly well.
We can also add more hardware.

Rust may force the agent to spend more time figuring out the first version of the program, even when he writes it. But earlier, the argument "Well, let's just add more hardware, and our Python or PHP will fly" could easily cost money. Hiring a developer to rewrite the service or spending your own time on it was more expensive than adding hardware.
Previously, RIIR (Rewrite It In Rust) looked more like a meme, and sometimes even like a cult: "Rewrite everything in Rust, and you'll be happy."
Now, part of the rewriting can be delegated to the agent. Especially if there are already tests that can check the new implementation and catch bugs and regressions.
You pay for the infrastructure every month.
If iron is becoming more expensive due to shortages and other crises, the argument for scaling up with more hardware is no longer as strong. So RIIR could very well be an economically justified solution. It's worth recalculating.
And for this purpose, it's not necessary to rewrite the entire project. You can start with one resource-hungry service. It doesn't even have to respond faster: after moving from Go to Rust, Grab's P99 latency remained about the same, but CPU usage for the same workload became significantly less.
However, you don't just have to count tokens. The new implementation still needs to be checked, implemented, and then maintained. If you're penny-pinching on hardware, even a cheap rewrite might not pay off.
Indeed, I'm hearing more often that generated code doesn't necessarily need to be maintained. If the model writes the implementation again in a month, why bother with the old one? It's easier to discard it and tell Claude/ChatGPT: "Do it the right way, and don't do it the wrong way."
Old code has another unpleasant feature: it may retain requirements that have already been forgotten. For example, a strange check once closed a rare bug or worked around a quirk of another API. Reformulating the task means also remembering all this good stuff.
If a script moved the files once, gave the correct result, and then went to the trash, that's fine. But if the program has users and data, the new implementation will have to be checked for compatibility.
The source code can be discarded. Behavior, data, and users can also be discarded, but there's a nuance.
In a service or library, I expect to return to the code many times. Therefore, I'm willing to spend more time on the first version, so that it's easier to notice if I or the agent missed something during subsequent changes. I want to receive code from the agent that I can understand and continue maintaining.

P.S. The multi-million B2B AI SaaS from the fifth chapter hasn't been written yet. Therefore, I'm leaving a link to my Telegram channel with IT memes: https://t.me/random_access_memes, thank you for your attention.
