18 ms·
I've been using Go more or less in every full-time job I've had since pre-1.0. It's simple for people on the team to pick up the basics, it generally chugs alon
by blixt 1y ago
I've been using Go more or less in every full-time job I've had since pre-1.0. It's simple for people on the team to pick up the basics, it generally chugs along (I'm rarely worried about updating to latest version of Go), it has most useful things built in, it compiles fast. Concurrency is tricky but if you spend some time with it, it's nice to express data flow in Go. The type system is most of the time very convenient, if sometimes a bit verbose. Just all-around a trusty tool in the belt.
But I can't help but agree with a lot of points in this article. Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences. That said, it's a _feeling_ I have, and maybe Go would be much worse if it had solved all these quirks. To be fair, I see more leniency in fixing quirks in the last few years, like at some point I didn't think we'd ever see generics, or custom iterators, etc.
The points about RAM and portability seem mostly like personal grievances though. If it was better, that would be nice, of course. But the GC in Go is very unlikely to cause issues in most programs even at very large scale, and it's not that hard to debug. And Go runs on most platforms anyone could ever wish to ship their software on.
But yeah the whole error / nil situation still bothers me. I find myself wishing for Result[Ok, Err] and Optional[T] quite often.
- guappa 1y ago> The type system is most of the time very convenient In what universe?
- theshrike79 1y agoIn mine. It's Just Fine. Is it the best or most robust or can you do fancy shit with it? No But it works well enough to release reliable software along with the massive linter framework that's built on top of Go.
- diarrhea 1y ago> massive linter framework I wonder why that ended up being necessary... ;)
- theshrike79 1y agoAll languages have linters, Go just has a proper ecosystem of them due to the way the language is built.
- diarrhea 1y ago$ golangci-lint linters | wc -l 107 That is a long list of linters (only a few enabled by default however). I much prefer less fragmented approaches such as clippy or ruff. It makes for a more coherent experience and surely much higher performance (1x AST parsing instead of dozens of times).
- xyzzyz 1y agoGo was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences. I'd say that it's entirely the other way around: they stuck to the practical convenience of solving the problem that they had in front of them, quickly, instead of analyzing the problem from the first principles, and solving the problem correctly (or using a solution that was Not Invented Here). Go's filesystem API is the perfect example. You need to open files? Great, we'll create func Open(name string) (*File, error) function, you can open files now, done. What if the file name is not valid UTF-8, though? Who cares, hasn't happen to me in the first 5 years I used Go.
- nasretdinov 1y agoNote that Go strings can be invalid UTF-8, they dropped panicking on encountering an invalid UTF string before 1.0 I think
- xyzzyz 1y agoThis also epitomizes the issue. What's the point of having `string` type at all, if it doesn't allow you to make any extra assumptions about the contents beyond `[]byte`? The answer is that they planned to make conversion to `string` error out when it's invalid UTF-8, and then assume that `string`s are valid UTF-8, but then it caused problems elsewhere, so they dropped it for immediate practical convenience.
- assbuttbuttass 1y agostring is just an immutable []byte. It's actually one of my favorite things about Go that strings can contain invalid utf-8, so you don't end up with the Rust mess of String vs OSString vs PathBuf vs Vec<u8>. It's all just string
- zozbot234 1y agoRust &str and String are specifically intended for UTF-8 valid text. If you're working with arbitrary byte sequences, that's what &[u8] and Vec<u8> are for in Rust. It's not a "mess", it's just different from what Golang does.
- traceroute66 1y ago> Just all-around a trusty tool in the belt I agree. The Go std-lib is fantastic. Also no dependency-hell with Go, unlike with Python. Just ship an oven-ready binary. And what's the alternative ? Java ? Licensing sagas requiring the use of divergent forks. Plus Go is easier to work with, perhaps especially for server-side deployments. Zig ? Rust ? Complex learning curve. And having to choose e.g. Rust crates re-introduces dependency hell and the potential for supply-chain attacks.
- theshrike79 1y agouv + the new way of adding the required packages in the comments is pretty good. you can go `uv run script.py` and it'll automatically fetch the libraries and run the script in a virtual environment. Still no match for Go though, shipping a single cross-compiled binary is a joy. And with a bit of trickery you can even bundle in your whole static website in it :) Works great when you're building business logic with a simple UI on top.
- richid 1y agoI've been out of the Python game for a while but I'm not surprised there is yet another tool on the market to handle this. You really come to appreciate when these batteries are included with the language itself. That Go binary will _always_ run but that Python project won't build in a few years.
- theshrike79 1y agoPeople tend to refer to the bit where Discord rewrote a bit of their stack in Rust because Go GC pauses were causing issues. The code was on the hot path of their central routing server handling Billions (with a B) messages in a second or something crazy like that. You're not building Discord, the GC will most likely never be even a blip in your metrics. The GC is just fine.
- AtlasBarfed 1y agoI get you can specifically write code that does not malloc, but I'm curious at scale if there are heap management / fragmentation and compression issues that are equivalent to GC pause issues. I don't have a lot of experience with the malloc languages at scale, but I do know that heat fragmentation and GC fragmentation are very similar problems. There are techniques in GC languages to avoid GC like arena allocation and stuff like that, generally considered non-idiomatic.
- torben-friis 1y agoMy feeling is that in terms of developer ergonomics, it nailed the “very opinionated, very standard, one way of doing things” part. It is a joy to work on a large microservices architecture and not have a different style on each repo, or avoiding formatting discussions because it is included. The issue is that it was a bit outdated in the choice of _which_ things to choose as the one Go way. People expect a map/filter method rather than a loop with off by one risks, a type system with the smartness of typescript (if less featured and more heavily enforced), error handling is annoying, and so on. I get that it’s tough to implement some of those features without opening the way to a lot of “creativity” in the bad sense. But I feel like go is sometimes a hard sell for this reason, for young devs whose mother language is JavaScript and not C.
- j1elo 1y ago> People expect a map/filter method Do they? After too many functional battles I started practicing what I'm jokingly calling "Debugging-Driven Development" and just like TDD keeps the design decisions in mind to allow for testability from the get-go, this makes me write code that will be trivially easy to debug (specially printf-guided debugging and step-by-step execution debugging) Like, adding a printf in the middle of a for loop, without even needing to understand the logic of the loop. Just make a new line and write a printf. I grew tired of all those tight chains of code that iterate beautifully but later when in a hurry at 3am on a Sunday are hell to decompose and debug.
- williamdclt 1y agoI'll agree that explicit loops are easier to debug, but that comes at the cost of being harder to write _and_ read (need to keep state in my head) _and_ being more bug-prone (because mutability). I think it's a bad trade-off, most languages out there are moving away from it
- nasretdinov 1y agoThere's actually one more interesting plus for the for loops that's not quite obvious in the beginning: the for-loops allow to do perform a single memory pass instead of multiple. If you're processing a large enough list it does make a significant difference because memory accesses are relatively expensive (the difference is not insignificant, the loop can be made e.g. 10x more performant by optimising memory accesses alone). So for a large loop the code like for i, value := source { result[i] = value * 2 + 1 } Would be 2x faster than a loop like for i, value := source { intermediate[i] = value * 2 } for i, value := intermediate { result[i] = value + 1 }
- apwell23 1y ago> But yeah the whole error / nil situation still bothers me. I find myself wishing for Result[Ok, Err] and Optional[T] quite often. I got insta rejected in interview when i said this in response to interview panels question about 'thoughts about golang' . Like they said, 'interview is over' and showed me the (virtual) door. I was stunned lol. This was during peak golang mania . Not sure what happened to rancherlabs .
- xigoi 1y agoOh my, you sure dodged a bullet.
- torben-friis 1y agoSome workplaces explicitly test cultural closeness to their philosophy of work (language, architecture, etc). It’s part trying to keep a common direction and part fear that dislike of their tech risks the hire not staying for long. I don’t agree with this approach, don’t get me wrong, but I’ve seen it done and it might explain your experience.
- laserlight 1y agoNo need to sugarcoat it. Some places are cults and it's best to avoid them. Good for GP.
- arccy 1y agoThey probably thought you weren't going to be a good fit for writing idiomatic Go. One of the things many people praise Go for is its standard style across codebases, if you don't like it, you're liable to try and write code that uses different patterns, which is painful for everyone involved.
- tgv 1y agoI find Result[] and Optional[] somewhat overrated, but nil does bother me. However, nil isn't going to go away (what else is going to be the default value for pointers and interfaces, and not break existing code?). I think something like a non-nilable type annotation/declaration would be all Go needs.
- blixt 1y agoYeah maybe they're overrated, but they seem like the agreed-upon set of types to avoid null and to standardize error handling (with some support for nice sugars like Rust's ? operator). I quite often see devs introducing them in other languages like TypeScript, but it just doesn't work as well when it's introduced in userland (usually you just end up with a small island of the codebase following this standard).
- tgv 1y agoTypescript has another way of dealing with null/undefined: it's in the type definition, and you can't use a value that's potentially null/undefined. Using Optional<T> in Typescript is, IMO, weird. Typescript also has exceptions... I think they only work if the language is built around it. In Rust, it works, because you just can't deref an Optional type without matching it, and the matching mechanism is much more general than that. But in other languages, it just becomes a wart. As I said, some kind of type annotation would be most go-like, e.g. func f(ptr PtrToData?) int { ... } You would only be allowed to touch *ptr inside a if ptr != nil { ... }. There's a linter from uber (nilaway) that works like that, except for the type annotation. That proposal would break existing code, so perhaps something an explicit marker for non-nil pointers is needed instead (but that's not very ergonomic, alas).
- bccdee 1y agoYeah default values are one of Go's original sins, and it's far too late to roll those back. I don't think there are even many benefits—`int i;` is not meaningfully better than `int i = 0;`. If it's struct initialization they were worried about, well, just write a constructor. Go has chosen explicit over implicit everywhere except initialization—the one place where I really needed "explicit."
- xtracto 1y agoI recently started writing Go for a new job, after 20 years of not touching a compiled language for something serious (I've done DevKitArm dev. as a hobby). I know it's mostly a matter of tastes, but darn, it feels horrible. And there are no default parameter values, and the error hanling smells bad, and no real stack trace in production. And the "object orientation" syntax, adding some ugly reference to each function. And the pointers... It took me back to my C/C++ days. Like programming with 25 year old technology from back when I was in university in 1999.
- pjmlp 1y agoAnd then people are amazed for it to achieve compile times, compiled languages were already doing on PCs running at 10 MHz within the constraints of 640 KB (TB, TP, Modula-2, Clipper, QB).
- remus 1y ago> [some] compiled languages were already doing on PCs running at 10 MHz within the constraints of 640 KB Many compiled languages are very slow to compile however, especially for large projects, C++ and rust being the usual examples.
- gf000 1y agoWell, spewing out barely-optimized machine code and having an ultra-weak type system certainly helps with speed - a la Go!
- remus 1y agoThat's a reasonable trade-off to make for some people, no? There's plenty of work to be done where you can cope with the occasional runtime error and less then bleeding edge performance, especially if that then means wins in other areas (compile speeds, tooling). Having a variety of languages available feels like a pretty good thing to me.
- 1y ago
- zozbot234 1y agoGolang is great for problem classes where you really, really can't do away with tracing GC. That's a rare case perhaps, but it exists nonetheless. Most GC languages don't have the kind of high-performance concurrent GC that you get out of the box with Golang, and the minimum RAM requirements are quite low as well. (You can of course provide more RAM to try and increase overall throughput, and you probably should - but you don't have to. That makes it a great fit for running on small cloud VM's, where RAM itself can be at a premium.)
- gf000 1y agoJava's GCs are a generation ahead, though, in both throughput-oriented and latency-sensitive workloads [1]. Though Go's GC did/does get a few improvements and it is much better than it was a few years ago. [1] ZGC has basically decoupled the heap size from the pause time, at that point you get longer pauses from the OS scheduler than from GC.
- Capricorn2481 1y agoDo you have a source for this? My understanding is Go's GC is much better optimized for low latency.
- BugsJustFindMe 1y ago> Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences. It feels often like the two principles they stuck/stick to are "what makes writing the compiler easier" and "what makes compilation fast". And those are good goals, but they're only barely developer-oriented.
- xg15 1y agoNot sure it was only that. I remember a lot of "we're not Java" in the discussions around it. I always had the feeling, they were rejecting certain ideas like exceptions and generics more out of principle, than any practical analysis. Like, yes, those ideas have frequently been driven too far and have led to their own pain points. But people also seem to frequently rediscover that removing them entirety will lead to pain, too.
- nine_k 1y agoIan Lance Taylor, a big proponent of generics, wrote a lot about the difficulties of adding generics to Golang. I bet the initial team just had to cut the scope and produce a working language, as simple as possible while still practically useful. Easy concurrency was the goal, so they basically took mostl of Modula-2 plus ideas form Oberon (and elsewhere), removed all the "fluff" (like arrays indexable by enumeration types, etc), added GC, and that was plenty enough.
- xg15 1y agoI feel especially with generics though, there is a sort of loop that many languages fall into. It goes something like this: (1) "Generics are too complicated and academical and in the real world we only need them for a small number of well-known tasks anyway, so let's just leave them out!" (2) The amount of code that does need generics but now has to work around the lack of them piles up, leading to an explosion of different libraries, design patterns, etc, that all try to partially recreate them in their own way. (3) The language designers finally cave and introduce some kind of generics support in a later version of the language. However, at this point, they have to deal with all the "legacy" code that is not generics-aware and with runtime environments that aren't either. It also somehow has to play nice with all the ad-hoc solutions that are still present. So the new implementation has to deal with a myriad of special cases and tradeoffs that wouldn't be there in the first if it had been included in the language from the beginning. (4) All the tradeoffs give the feature a reputation of needless complexity and frustrating limitations and/or footguns, prompting the next language designer to wonder if they should include them at all. Go to (1) ...
- giantg2 1y ago"Concurrency is tricky" This tends to be true for most languages, even the ones with easier concurrency support. Using it correctly is the tricky part. I have no real problem with the portability. The area I see Go shining in is stuff like AWS Lambda where you want fast execution and aren't distributing the code to user systems.
- Mawr 1y ago> I find myself wishing for Optional[T] quite often. Well, so long as you don't care about compatibility with the broad ecosystem, you can write a perfectly fine Optional yourself: type Optional[Value any] struct { value Value exists bool } // New empty. func New[Value any]() Optional[Value] {} // New of value. func Of[Value any](value Value) Optional[Value] {} // New of pointer. func OfPointer[Value any](value *Value) Optional[Value] {} // Only general way to get the value. func (o Optional[Value]) Get() (Value, bool) {} // Get value or panic. func (o Optional[Value]) MustGet() Value {} // Get value or default. func (o Optional[Value]) GetOrElse(defaultValue Value) Value {} // JSON support. func (o Optional[Value]) MarshalJSON() ([]byte, error) {} func (o *Optional[Value]) UnmarshalJSON(data []byte) error {} // DB support. func (o *Optional[Value]) Scan(value any) error {} func (o Optional[Value]) Value() (driver.Value, error) {} But you probably do care about compatibility with everyone else, so... yeah it really sucks that the Go way of dealing with optionality is slinging pointers around.
- bccdee 1y agoYou can write `Optional`, sure, but you can't un-write `nil`, which is what I really want. I use `Optional<T>` in Java as much as I can, and it hasn't saved me from NullPointerException.
- Mawr 1y agoYou're not being very precise about your exact issues. `nil` isn't anywhere as much of an issue in Go as it is in Java because not everything is a reference to an object. A struct cannot be nil, etc. In Java you can literally just `return null` instead of an `Optional<T>`, not so in Go. There aren't many possibilities for nil errors in Go once you eliminate the self-harm of abusing pointers to represent optionality.
- bccdee 1y agoPointers are pretty common in Go though. Not as common as Java, granted—you're not NPEing on an integer—but they still get passed around a decent amount.
- bwfan123 1y ago> Concurrency is tricky The go language and its runtime is the only system I know that is able to handle concurrency with multicore cpus seamlessly within the language, using the CSP-like (goroutine/channel) formalism which is easy to reason with. Python is a mess with the gil and async libraries that are hard to reason with. C,C++,Java etc need external libraries to implement threading which cant be reasoned with in the context of the language itself. So, go is a perfect fit for the http server (or service) usecase and in my experience there is no parallel.
- dcrazy 1y agoSwift? JavaScript?
- nothrabannosir 1y agoJavaScript? How, web workers? JavaScript is M:1 threaded. You can’t use multiple cores without what basically amounts to user space ipc
- mhink 1y agoNot to dispute too strongly (since I haven't used this functionality myself), but Node.js does have support for true multithreading since v12: https://nodejs.org/dist/latest/docs/api/worker_threads.html https://nodejs.org/dist/latest/docs/api/worker_threads.html. I'm not sure what you mean by "M:1 threaded" but I'm legitimately curious to understand more here, if you're willing to give more details. There are also runtimes like e.g. Hermes (used primarily by React Native), there's support for separating operations between the graphics thread and other threads. All that being said, I won't dispute OP's point about "handling concurrency [...] within the language"- multithreading and concurrency are baked into the Golang language in a more fundamental way than Javascript. But it's certainly worth pointing out that at least several of the major runtimes are capable of multithreading, out of the box.
- odo1242 1y agoI had to look M:1 threading up too - it's this: https://en.wikipedia.org/wiki/Thread_(computing)#M:1_(user-level_threading) https://en.wikipedia.org/wiki/Thread_(computing)#M:1_(user-l... Basically OP was saying that JavaScript can run multiple tasks concurrently, but with no parallelism since all tasks map to 1 OS thread.
- whalesalad 1y agoThe remarkable thing to me about Go is that it was created relatively recently, and the collective mindshare of our industry knew better about these sorts of issues. It would be like inventing a modern record player today with fancy new records that can't be damaged and last forever. Great... but why the fuck are we doing that? We should not be writing low level code like this with all of the boilerplate, verbosity, footguns. Build high level languages that perform like low level languages. I shouldn't fault the creators. They did what they did, and that is all and good. I am more shocked by the way it has exploded in adoption. Would love to see a coffeescript for golang.
- Mawr 1y ago> Would love to see a coffeescript for golang. It's not viable to use, but: https://github.com/borgo-lang/borgo https://github.com/borgo-lang/borgo
- yubblegum 1y ago> Concurrency is tricky but You hear that Rob Pike? LOL. All those years he shat on Java, it was so irritating. (Yes schadenfreude /g)
- fulafel 1y ago> But I can't help but agree with a lot of points in this article. Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences I think this is a fine "fail-closed" way of language design. For example, Python has gone the other way and language complexity has gotten pretty bad since the small-language days. Trust what you are, don't try to please everyone, lest you become something like C++. Clojure is good in this respect.
- jjav 1y agoGo is all right. But in a world where java exists, I still have not seen any reason to consider go.
- simon_void 1y agoIn a worlde where kotlin exists, I still have not seen any reason to consider java XD
- j45 1y agoI wonder if Go was setup in part to help large, complex or critical codebases on even older old school syntaxes progress relative to where they are.