What question is this article actually asking?
Hello, everyone. Software development appears to have entered a new era. Wait… no. We are already in it. Developers used to type code one line at a time. Now we can hand requirements to a coding agent and let it read the project, edit code, run commands, write tests, and keep iterating while we sit back with coffee or cook something. Easy life… maybe?
Sometimes it works shockingly well. Other times it does something goofy outside the scope and leaves us wondering whether the documentation is stale or the agent has been sniffing glue. Ha.
First, let me set the scope of this article.
This is not a comparison of Rust and Go as languages for building an AI agent. The question is:
In an era when we let AI and coding agents write production code—whether it is a backend, web service, game, CLI, desktop application, or anything else—which target language should we choose between Rust and Go?
My answer is Rust.
In the age of agentic AI, the cost of generating code keeps falling—or so I hope—while the cost of verifying code becomes a larger problem every day.
When AI can produce code at something approaching light speed—yes, that is only a metaphor—we do not merely need a language that is quick to write. We need compiler-level verification that can smack down code with type, concurrency, and memory-safety problems before it gets anywhere near production.
What about you, reader? Do you see it the same way?
This is where Rust has a ridiculously clear advantage over Go. You can probably tell from Mars that I am biased toward R…
TL;DR for readers in a hurry
If AI is going to write a growing share of our production code, I choose Rust because it provides a substantially thicker verification layer at compile time. That includes:
- Ownership and the borrow checker
- Memory safety without a garbage collector, which can add CPU and latency overhead in some workloads
SendandSyncfor controlling which types may move or be shared across threads- Pattern matching over enum states
Result<T, E>for forcing errors into the type flow- Trait bounds for constraining generic code
cargo checkfor checking code quickly—at least, I think so—before building a final binary- Clippy for catching suspicious patterns and additional code smells
Rust does not remove the need for code review. Anyone claiming that Rust makes it safe to merge AI-generated code without looking at it is bluffing. That is not bravery; it is carelessness, plain and simple.
Even so, Rust really can reduce the surface area humans must review manually. I am not making that up—at least for the classes of defect its type system and compiler can check.
We can let the compiler check types, ownership, lifetimes, and concurrency, then spend human time on what the compiler cannot know:
- Is the business logic correct, or did the AI hallucinate a requirement?
- Is the code well structured, with clear domain boundaries? Is it SOLID?
- Is authorization strong enough, or can an attacker bypass it?
- Does the implementation cover what the business team actually requested?
That is the central idea of this article:
Rust does not reduce human review to zero. It gets rid of some technical review work—types, ownership, memory safety, that sort of thing—so humans can focus on the work that genuinely needs their time.
Go still offers a simple syntax, fast compilation, easy onboarding, and a strong backend ecosystem. But Go’s simplicity was designed to help humans write code quickly, more than to save us from painful surprises at runtime.
When the primary code writer becomes an agent that can keep fixing compiler errors all day without complaining or begging for more caffeine, the strictness once considered a Rust liability becomes more valuable immediately.
So, if AI can take more of the pain out of writing difficult code, why are we still using “Go is easier to learn and faster to write than Rust” as the excuse? The one getting the headache is increasingly the agent, not the human.
How does agentic AI change the software-development equation?
The old software-development flow looked something like this: humans spent most of the time thinking and typing code, so a language that was easy to write and quick to compile had an obvious advantage. I think.
Once a coding agent enters the picture, the flow changes.
When agents write the code, the cost shifts from typing to verification
Humans move up to framing the problem and reviewing what the compiler cannot know, while the agent can keep acting on toolchain feedback.
- 01Think and design
- 02Write code
- 03Receive compiler errors
- 04Fix the code
- 05Review and test
- 06Ship
Writing and fixing syntax consumes meaningful human time, so approachable languages and fast compilation help directly.
- 01Human frames the plan and boundaries
- 02Agent writes code
- 03Toolchain returns diagnostics
- 04Agent fixes and checks again
- 05Human reviews logic and risk
- 06Ship
Repeat until checks pass
A language is now valuable not only when it is easy to write, but when its feedback blocks more defects before runtime.
A comparison between a human-driven development loop and one in which a coding agent acts directly on toolchain feedback.
The agent never gets annoyed by the compiler, bored with the borrow checker, furious at lifetimes, or needs to go home because a trait bound ran longer than one line.
Whatever error the compiler throws back, the agent can fix it and ship another attempt. So the important question is no longer only:
Which language makes it easier for the agent to write code?
It should also be:
Which language provides feedback strong enough to push the agent toward safer code before it hands the work back to a human?
From this perspective, Rust has a major advantage.
cargo check checks a package and its dependencies with the compiler while skipping final code generation. That makes it faster than cargo build and very well suited to a feedback loop that runs repeatedly.
It is not formal verification, and some diagnostics appear only during code generation or at runtime. Even so, it catches enough to act as a tireless first reviewer—one that never gets lazy and never clicks Approve out of habit.
Passing the Go compiler does not mean the code is ready to run
I am not saying that Go lacks a type system or that its compiler checks nothing. The Go compiler catches syntax errors, type mismatches, missing imports, and many other problems as expected.
But important failures—especially in concurrent programs—can still compile and then blow up at runtime.
Consider this small example:
package main
import "sync"
func main() {
counts := map[string]int{}
tools := []string{"search", "database", "email"}
var wg sync.WaitGroup
for _, tool := range tools {
wg.Add(1)
go func() {
defer wg.Done()
counts[tool]++
}()
}
wg.Wait()
}
This code compiles.
But several goroutines write to the same map without synchronization. That is a data race and may end in a runtime failure.
Go has a good race detector, available through go test -race and go run -race. However, Go’s own documentation states clearly that it only finds races that actually occur at runtime.
If the test never exercises the path, or the timing does not trigger the race during that run, the detector may not find it.
The Go Code Review Comments also warn that goroutines can leak while blocked on channel operations, and that the garbage collector will not terminate such a goroutine for us.
Now look at the same idea in safe Rust:
use std::thread;
fn main() {
let mut completed = 0;
thread::scope(|scope| {
scope.spawn(|| completed += 1);
scope.spawn(|| completed += 1);
});
}
Rust refuses to compile this because the code attempts to hold more than one mutable borrow of completed at the same time.
The compiler does not redesign the architecture for us, but it forces us to choose an explicit sharing model, such as:
- Moving ownership of the data into a worker
- Returning results and combining them later
- Using message passing
- Using an atomic type
- Using
Arc<Mutex<T>>when shared mutable state is truly necessary
That difference matters.
Go provides a tool that can find a data race when the relevant path runs. Safe Rust does not even give this shape of shared mutable state permission to compile, through ownership and the Send/Sync model described in Fearless Concurrency.
For human-written code, we might say, “Fine, we will write comprehensive tests and review it carefully.” But when an agent can generate hundreds of lines of concurrent code at a time, compiler guarantees can reduce human review load more than readability alone. You agree, right, dear reader?

Invalid states: bugs a type system can eliminate before they exist
Beyond memory and concurrency, another reason Rust works well for AI-generated code is state modeling.
Suppose we are building an order system for a web application in Go:
type Order struct {
Status string
PaymentID *string
FailureReason *string
ShippedAt *time.Time
}
This struct is flexible, but it also permits strange combinations:
Status == "paid"whilePaymentID == nilStatus == "failed"without aFailureReason- An order that has shipped but still says
pending_payment - A
FailureReasonandShippedAtat the same time
We can address this with constructors, validation, and unexported fields. Go does not force us to design a bad model. But what happens when an agent keeps adding new fields and flows like this? Do humans have to keep checking those conventions forever without getting bored? Sure, we can add more instructions for the agent, but that burns more tokens too.
Rust lets us encode the shape of each state more directly with an enum:
enum OrderState {
PendingPayment,
Paid {
payment_id: PaymentId,
},
Failed {
reason: PaymentFailure,
},
Shipped {
payment_id: PaymentId,
shipped_at: DateTime<Utc>,
},
}
Here, Paid cannot exist without a PaymentId, and Failed must carry a PaymentFailure, because the type requires those values.
If the agent adds a new state and another function has a match that does not cover it, the compiler reports a non-exhaustive pattern and sends us to every affected location.
The same idea applies to games:
enum PlayerState {
Alive { hp: NonZeroU16 },
Downed { revive_deadline: Instant },
Dead,
}
We no longer need is_dead, is_downed, hp, and revive_time scattered across several flags while hoping they never contradict one another. The faster an agent adds features, the more valuable it becomes to make invalid states impossible to construct.
Refactoring AI-written code: the compiler should be a guiding light, not stand there laughing
Refactoring is another common coding-agent task. Suppose we change UserId from a plain String into a domain type, or change a function that returned nothing into one that returns Result.
In Rust, the agent can change the source type first, run cargo check, and use compiler errors as a map of the call sites that still need work.
struct UserId(uuid::Uuid);
fn delete_user(id: UserId) -> Result<DeletedUser, DeleteUserError> {
// ...
}
The compiler immediately identifies call sites that still pass a String, fail to handle the Result, or try to use a value after ownership has moved.
The Go compiler catches signature mismatches too, so that part is not unique to Rust. Rust can encode deeper constraints in types, however, and exhaustive pattern matching makes a change to a state model propagate clearly to its consumers.
For an agent, this is structured feedback. It does not have to infer from a runtime log which flow forgot the new case… In a large project, compiler errors start to act like a dependency graph showing where a contract change has consequences.
What about web backends—does Rust still have the advantage?
Hell yes!!! A web backend is not just dumb CRUD. It also contains business states, concurrent work, queues, caches, permissions, and everything but the kitchen sink…
We might use Axum for HTTP, Tokio for the async runtime, SQLx for the database, and Serde for serialization, with clear type boundaries around requests, domain models, errors, and responses.
The benefit is not performance alone. An agent can use the compiler to check integration between layers:
- A handler cannot pass the wrong input type into a use case
- A use case cannot silently ignore a repository error when the API returns
Result - Shared state that is not
Send + Syncis rejected by an async server that requires those bounds - Adding a variant to an error enum exposes matches that are no longer exhaustive
- Resources are cleaned up according to ownership and RAII when they leave scope
RAII ties the lifetime of a resource to an object or variable. When the variable leaves scope, its destructor runs and releases the resource according to that type’s rules.
And because I do not want to praise Rust too much, let me say this plainly: Go is still a perfectly good backend language. If a team already has many Go services, moving all of them to Rust merely because of this article would be a bad idea. The migration cost and risk are unlikely to pay for themselves, so save Rust for new projects instead. 🙏🙏🙏 In that case, I see Rust as a long-term safety harness. 🙏🙏🙏
Where does Go still fit in this era?
At this point, you may think I am about to call Go garbage and tell everyone to move to Rust. Not so fast. Go remains an excellent choice when:
- The company already has a strong Go codebase and team
- The service is straightforward glue code between APIs
- Very fast build times matter in a large monorepo
- A required SDK or infrastructure library supports Go substantially better
- The component has a narrow failure domain and limited side effects
- Team conventions, tests, static analysis, and observability are strong
If a Go service has run in production for years, has solid tests, has a team that owns it, and is not causing problems, rewriting it in Rust to make the architecture look fashionable is simply burning money. For greenfield production code that AI will generate and refactor at a high volume, however, Rust is still my clear first choice.
Go’s “easy to write” advantage carries less weight now. We can let the agent do the writing instead of taking 100% of that headache ourselves, while Rust keeps the advantage of stricter compiler checks. Compared with the trade-off in writing difficulty, I think that is well worth it.
But, but, but—do not forget that Rust still does not mean we can stop reviewing code
This point needs repeating before someone uses the article as an excuse to merge code without reviewing anything. The Rust compiler does not know:
- Whether the agent misunderstood the requirement from the beginning
- Whether a UI flow makes it too easy for users to make a mistake
- Whether a 20% discount should be applied before or after tax
- Whether this user is allowed to delete the project
- Or any other rule outside the type system
Rust can still deadlock. It can still have logic races, leak memory through some reference cycles, panic, and create undefined behavior when unsafe is used incorrectly.
So “less review” means:
Humans should spend less time rechecking what the compiler can already prove.
Human review should move up to the domain, security, data migration, performance, and user-impact levels instead of checking every line for whether a value moved or a match covered every state. If we are going to inspect all of that manually, we might as well get another coffee.
That is what using AI to write code responsibly looks like…
The pipeline I want the coding agent to finish before it hands the work to us
To get the benefit of Rust in the agentic-AI era, we cannot let the agent flatter us with “You’re absolutely right!” all day. It should pass at least this much of the toolchain before presenting its work:
The pipeline a coding agent should pass before handoff
The compiler is an important gate, but production readiness needs several gates and still ends with human review.
- 01Formatting
cargo fmt --all -- --checkKeep diffs readable and remove formatting noise before review.
- 02Compiler
cargo check --workspace --all-targetsCheck types, ownership, and declared targets without producing final binaries.
- 03Linting
cargo clippy --workspace --all-targets -- -D warningsTurn the selected lint policy into CI failures.
- 04Tests
cargo test --workspaceRun the unit and integration tests included in the workspace.
- 05Project checks
Exercise contracts, migrations, feature combinations, and external services for the real system.
- 06Human review
Review requirements, business logic, security, data migration, performance, and user impact.
This minimum sequence is only a starting point. Each project must add checks for its own failure modes and environment.
If a project has feature flags, build a CI matrix for the combinations it actually supports. Do not add --all-features without thinking: some features may be intentionally mutually exclusive.
Review unsafe separately and add tools appropriate to the work.
Test both up and down paths for database migrations.
For web systems, test contracts with the database and external services.
…blah blah.
The compiler is an important gate, but production readiness is a pipeline, not a single command. Rust’s advantage is that each agent edit can produce detailed, machine-readable feedback for another iteration without requiring a human to resolve every error.
Conclusion
When AI writes a growing share of our production code, which language should we choose to control the quality of the result? My answer is Rust.
Rust has historically been criticized for being difficult to write, for having a fussy compiler, and for making developers fight ownership more than Go does. When an agent writes the code and resolves compiler errors, that fussiness no longer falls entirely on human shoulders. We keep the guarantees from the type system, ownership, the borrow checker, Send, Sync, exhaustive matching, and Result.
Put simply, AI lowers the cost of writing Rust without removing the benefits of the Rust compiler.
Conversely, Go’s major advantages in ease of writing and needing fewer developers carry less weight when an agent can generate boilerplate and keep repairing code.
If I were starting a greenfield project today—a web backend, game, CLI, desktop tool, or systems service—and expected coding agents to write a large share of it, I would default to Rust first.
Not to eliminate review, but to reduce the technical review that the compiler can handle. Then we can spend our time on coffee instead of manually checking every line for memory safety while still waiting to see whether runtime punches us in the face. Ba-dum-tss!!!
I hope the article was useful. Thank you for reading all the way to the end. 💀
References
- The Cargo Book —
cargo check - The Cargo Book —
cargo test - Clippy Documentation
- The Rust Programming Language — Ownership
- The Rust Programming Language — Fearless Concurrency
- The Rust Programming Language —
SendandSync - Go Data Race Detector
- Go Code Review Comments — Goroutine Lifetimes
- The Go Memory Model

