GoLazy Blog
Why GoLazy is built in Go—not Python, Ruby, or Rust
GoLazy chooses Go for the person who has to understand, review, and maintain an application after an agent writes it.
An agent can write an application faster than I can read it. That makes the language choice more important, not less.
I chose Go for the person who has to understand the code after the agent writes it. That person might be me tomorrow, a colleague next month, or somebody inheriting the application in three years.
The question is practical: how much context must that person load before they can review a change?
Start with a call you can follow
Consider a service method:
func (s *Service) List() []Ticket {
return []Ticket{
{Title: "Reset my password", Status: "open"},
}
}
This is fixed sample data, not a persistence layer. But the boundary is visible: the service returns tickets. A controller calls it, and a view renders the result. Add storage later and that responsibility still belongs to the service.
Go's language specification defines typed values, package-qualified names, and explicit imports. If an agent changes List to return ([]Ticket, error), callers that still expect one value must be updated before the program compiles. A package import tells the reviewer where an external name comes from.
The compiler does not know whether a ticket should be visible to this user. Types narrow some mistakes; authorization rules still need review and tests.
GoLazy adds services, views, and controllers to that foundation. The layout gives the agent a convention to follow and the reviewer a place to look.
Python and Ruby offer useful flexibility
Python can customize attribute lookup through __getattr__, __getattribute__, and descriptors. An expression such as ticket.owner can run code rather than read a stored attribute. Ruby's method_missing can handle a call whose method is not explicitly defined on the receiver.
These mechanisms support useful APIs. They can also require a reviewer to understand a lookup hook, a framework convention, or runtime registration before finding the behavior behind an ordinary-looking expression.
Neither language forces an application to use those mechanisms. Explicit Python or Ruby can be very readable. Python also has type annotations and static checking tools; the runtime itself does not enforce those annotations.
Go has reflection and interface dispatch too, and GoLazy uses reflection for some routing and command conventions. We still need to explain those conventions. Choosing Go is a preference for the constraints around the application code, not a claim that indirection disappears.
Rust gives stronger ownership guarantees
Rust's ownership rules and borrowing rules enforce memory access constraints at compile time. For example, the rules constrain simultaneous mutable and shared references. Those are valuable guarantees.
They also give an application author more concepts to account for when data is shared or lifetimes interact. For this framework's web application workflow, I prefer Go's garbage-collected model and a smaller conceptual burden around everyday service code. That is a product decision, not a universal ranking of languages. Rust may be a better fit when ownership control is central to the problem.
Shared tools reduce incidental decisions
Go's formatting convention removes many style decisions. The Go command provides standard build, test, and vet workflows. That gives agents and humans a common vocabulary for checking a change.
The costs remain visible. Go asks for explicit error handling. Its type system cannot prove business rules. Garbage collection has runtime costs. Some abstractions are more verbose than their Python or Ruby equivalents, and some ownership errors that Rust rejects need other checks in Go.
Go does not make model output deterministic. It does not make generated code correct. A successful build is a useful checkpoint, followed by tests and a human review of behavior.
The promise I want GoLazy to keep is smaller: after the agent finishes, you should have an application you can still follow. Try the support ticket example, then inspect the calls yourself.