Go is more about making it easier to build fast and robust systems. But it really doesn't care if the code itself is ugly and full of boilerplate.
As I've gotten more experience, I've come to really appreciate Go's tradeoffs. It's not as fun up front, but on the other hand, you're less likely to get server alerts at 4am. It really depends what you're building though.
though Ruby's expressiveness comes at a cost and I'd personally stick with Go in a team but use something like RubyLLM for personal projects
Ruby and go each prioritize different parts of the developer experience. Ruby prioritizes the experience of the author of the initial code at the expense of the experience of the maintainer who comes later. Go prioritizes the experience of a maintainer over the experience of the initial author. Both prioritize a developer, both de-prioritize a different developer, and which one makes more sense really depends on the ratio of time spent on writing greenfield code versus maintaining something another human wrote years ago who's long gone.
I find changes in existing Go software often end up spreading far deeper into the app than you'd expect.
The runtime is fantastic, though, so I don't see it losing it's popularity anytime soon.
That’s kind of just the surface level of maintenance though. Go is not so much focused on making it easy to read a single file, but on minimizing the chains of abstraction and indirection you need to follow to understand exactly how things work.
It’s much more likely that all the logic and config to do something is right there in that file, or else just one or two “Go to definition” clicks away. You end up with way more boilerplate and repetition, but also looser coupling between files, functions, and components.
Contrast that to a beautiful DSL in Ruby. It’s lovely until it breaks or you need to extend it, and you realize that a small change will require refactoring call sites across a dozen different files. Oh and now this other thing that reused that logic is broken, and we’ve got to update most of the test suite, and so on.
This is the biggest part of it: maintainers need static analysis and/or (preferably and) very good grepability to help them navigate foreign code. Ruby by its nature makes static analysis essentially impossible to do consistently, whereas Go leans to the opposite extreme.
// Must[T](T, error) T is necessary because of Go error handling differences
chat := Must(gollm.Chat().WithModel("claude-3-7-sonnet-20250219"))
resp := Must(chat.Ask("What's the difference between an unexported and an exported struct field?"))
resp = Must(chat.Ask("Could you give me an example?"))
resp = Must(chat.Ask("Tell me a story about a Go programmer"))
for chunk := range resp { // Requires Go 1.23+ for iterators
fmt.Print(chunk.Content)
}
resp = Must(chat.WithImages("diagram1.png", "diagram2.png").Ask("Compare these diagrams"))
type Search struct {
Query string `description:"The search query" required:"true"`
Limit int `description:"Max results" default:"5"`
}
func (s Search) Execute() ([]string, error) { ... }
resp = Must(chat.WithTool[Search]().Ask("Find documents about Go 1.23 features"))
And so on. Syntax is different, of course, but semantics (save for language-specific nuances, like error handling and lack of optional arguments) are approximately the same, biggest difference being WithSomething() having to precede Ask()