Hacker News new | past | comments | ask | show | jobs | submit login
Go runtime: 4 years later (go.dev)
328 points by spacey on Sept 26, 2022 | hide | past | favorite | 284 comments



I really like the engineering principles in general that the Go team uses, however, I just don't like Go. That isn't meant as a slight or anything other than simply my opinion. That said, I really like the idea of a simple language based on the sort of principles demonstrated here. The runtime seems really nice, I just wish I liked the language better (IMO: not expressive enough, needs better error handling, needs much better enums, needs pattern matching, has error prone C-style 'for', almost everything is statements not expressions, special comments are error prone, has nil pointers, etc.). In the end I think if someone were to write a slightly simpler version of Rust with the Go runtime it might be pretty neat. That said, I don't know what I'd want to drop from Rust so maybe I'm just fantasizing.


I haven't found any issues with expression, so far. I wouldn't use it to write a UI, but for writing networking code or automated tasks I find it perfectly suited to the task.

I appreciate it's error handling. It's burdensome, sure, but it presents almost no additional cognitive load when attempting to reason about control flow.

It essentially has no enums. However, it has a comfortable type system that can wrap primitive types, and you can define methods on those wrapped types. It's serviceable, but not elegant.

I have never been a fan of pattern matching outside of functional languages. The phenomenon I notice in languages that do have it, is the majority of uses cases seem to be a pattern match with two outcomes, one a Some() and the other a None(). It really seems like a more annoying way to write if {} else {}.

It has for, but it also has 'range.' It makes up for a lot.

It has nil pointers, which are just a "zero value." It's not uncommon to make it useful and safer in certain contexts. You can request a lookup in a map that is nil. You can ask for the length of a slice that is nil. You get sensible answers still. There are still footguns, but they're all single barrel.. which is _nicer_.

I don't need a perfect language. Good enough with low cognitive load and amazing cross-compile facilities I'll take any day of the week.


I will agree Rust has a higher "cognitive load", but not if you write it every day. I think Rust might be tough to leave and come back say a year later, but Go admittedly would be easy. That said, having written 50K+ code in both languages, I never want to write Go again. Rust on the other hand is all I want to write now. I do wonder how many people who understand Go's limitations (and work around them as you point out above) have truly given Rust a try (takes a few months - can't be done faster). I can't imagine anyone would want to go back tbh, but maybe I'm wrong.

EDIT: In fairness, there is one thing and exactly one thing I miss about Go: compile times. Rust is simply no competition here, but in trade, it is doing a ton of extra work. Trade offs.


> I do wonder how many people who understand Go's limitations (and work around them as you point out above) have truly given Rust a try (takes a few months - can't be done faster)

I have written both languages professionally in various contexts for years. In point of fact, I've been writing Rust since before Rust 1.0 came out, although I didn't get my first Rust job until about 3 years later. They're both excellent languages. I actually didn't learn Go until several years after learning Rust, and at the time I didn't appreciate it like I should have, since I was so excited about Rust. Only after getting professional experience with Rust did I truly begin to appreciate how nice Go is for the things it is good at.

Go is just so much better suited for network-connected services (including but not limited to web backends), and Rust is better suited for batch processing or embedded contexts where no compromise on performance can be made, in my experience. There are lots of reasons for this, but it's off topic from the question, and the answer to the question is that I absolutely do go back to Go. Neither language is a perfect tool for all problems, and I constantly dream about building my own language that learns from the best of both... there is certainly room for improvement on both ends.


I have a similar experience. Used to write a lot of golang, and now I write a lot of Rust. I think I would have pointed out the same issues: auditing Golang network code you can often find easy DoS attacks due to nil values being dereferences (protobuf I’m looking at you), there’s no sum types or enums, etc. Still though, I miss how easy it is to read golang and ramp up on a codebase. Even today I sometimes come across a golang codebase and look for something and I can always easily follow the code. Eventhough I’ve written a lot of Rust, it’s always challenging to ramp up on a Rust codebase, and the language is often abused because it’s too expressive. I’d still say Rust is so much more pleasant to write due to that expressiveness. It really is a double edge sword.


It shouldn't be a Rust vs golang. golang is mostly a somewhat better python, and mostly for writing backend services.

You can compare its domain (to a certain degree) to Java or C#, in which case the latter two are superior due to the reasons mentioned previously (enums, pattern matching, etc.).


Agree, about the "us vs them" mentality. The tribalism has gotten out of hand, though I kind of suspect some of it is corporate interests being behind it to push the illusion its a "winner take all" game and "putting the batteries in the backs" of many of the evangelists.


It feels like the language wars of 90s and 00s all over again, and I personally don't want to go through that cycle again.

I don't want to take sides, I want to enjoy programming and learning, that's all. When zealots zeal, kinder people either leave or go underground.


The problem with rust is explicitly that cognitive load in large codebases. It is much easier to read go code than rust when working on many things. Rust fits great if you don't scale though.


It’s funny how experiences differ; I can’t comment on how Go fares here, but if anything, I find that the compiler takes such a mental load off of my shoulders that Rust is the language I find easiest to make large refactorings in.


It's not about refactoring for me. It's about trying to grok what the heck some library author or coworker was thinking when they went all Architecture Astronaut with the type system and traits. It reminds me of how people go crazy with OO and end up with delegation spread across several files. I already have to hold the problem in my head. I find that Go takes such a mental load off my shoulders that I find it the easiest to grok other's intentions (including my own several months/years later) in.


It may depend on the problem at hand as well. I never really bought this “each line is easier to comprehend” reasoning, because then ad absurdum we would all be writing assembly.

But sure, one is a low level language which can and thus must care about every little detail, while the other is a managed language. (And honestly, mixing the two as if they share the same niche is very off putting)


That's fair. In my mind, they're at least related: if I'm trying to learn about some code, I change stuff and see what breaks.


Just as parent, my greatest struggle with learning Rust is to open someone else's code and trying to understand what is going on. With C that's a lot easier.

I you were to learn Rust from scratch, what project would you recommend for the "changing and breaking" approach?


I don't know if I use that to learn the language itself, but also, different people have different learning styles. What I personally do to learn new languages is write some sort of program that I know well. So for me, that's text adventure games. When I'm trying to learn a new language, I go and make a very simple one. This helps because you're not learning a new domain and a new language at the same time.

But along the whole "break it" idea... I don't know if you're an IDE person or a text editor die-hard, but I've found that rust-analyzer helps a ton. I'm historically a "vim with no plugins" kind of guy, but I'm using VS: Code with the vim keybindings now, and even if I'm not changing some code and getting feedback from the compiler itself, using rust-analyzer to go "hey what's this type here? Where's it defined, let's go take a look" has helped a ton.

That said, types help and poking around helps, but it's not always a panacea. Today I'm working on fixing something that doesn't quite work, even though yesterday I figured out how to assemble everything I'm supposed to need from this library I'm using. "It compiles it works" is a thing people say, and while I feel that way often, it's not true all of the time, of course.


The differentiating factor is probably prior exposure to C++. If you have used C++ then Rust is so easy and convenient and fun. It takes hideous C++ monstrosities and turns them into easy one liners.

If you have never used C++ and your baseline is Go or JavaScript then it probably looks like a confusing hellscape. You have to learn a whole new kind of type system, a new nomenclature (“Vec”s instead of “Array”s), new concepts like allocators, plus the borrow checker and lifetime annotations, and module/crate system. Former C++ programmers only have to learn the latter half.


> The differentiating factor is probably prior exposure to C++. If you have used C++ then Rust is so easy and convenient and fun. It takes hideous C++ monstrosities and turns them into easy one liners.

Obviously you didn't mean that as a universal rule, but for posterity, I'm a counter-example to that. I learned Rust before learning C++ or Go. If anything, I think this biased me _more_ towards Rust than the others; C++ just kind of felt like a more error-prone, less ergonomic Rust, and Go just felt like it took far too much boilerplate to get anything done.


>If you have never used C++ and your baseline is Go or JavaScript then it probably looks like a confusing hellscape.

I'm not sure this is accurate - have you seen modern TypeScript? I actually find that more confusing than Rust to read.

(FWIW, I was predominantly Python/JS-centric before just going "all in" on Rust. It's not that bad, in my experience - if anything, I find Rust "just works" whereas I got tired of the churn in those environments)


Yes, TypeScript can get very complex too. But there is little overlap in the complexity of TypeScript, which mostly comes from learning how to program within the structural type system; and Rust which is about memory management without a GC.

Rust and C++ are like Hebrew and Arabic. Difficult languages for an outsider, but if you speak one you have a significant head start towards learning the other. TypeScript is Japanese: knowing Japanese doesn’t help you speak Arabic, despite both being difficult.


I hate C++ but got used to Rust. It just has a steep learning curve unlike Golang. Takes a while but when you get there it is really pleasant.

On the other hand Golang can be fun in like a week of learning it.


Not really. I come from TypeScript and started writing Rust, and they're quite similar in terms of type expressiveness. With both I feel the same level of comfort of refactoring.

Meanwhile Go doesn't even have algebraic data types. I can't imagine working with a language that doesn't have these kinds of functional features anymore after having gotten used to them.


Async code is way nicer in Go than it is in Rust. It makes up for a lot for me when writing web services. Also Go is much easier to read vs Rust.


> Async code is way nicer in Go than it is in Rust.

That’s because Go does not have async code. It has sync code on an async runtime. Like e.g. Erlang/Elixir.


Exactly don't have to deal with red/blue functions


This is an interesting comment. Is there a webpage where I can read more about this idea?



That guy is a lengendary blogger! And I have read that post. It was very eye opening. I always struggle when I write an (internal) API that has sync (blocking) and async (non-blocking) sections and methods. It quickly becomes very difficult to reason about!


>Also Go is much easier to read vs Rust.

This is subjective - I for example have no real issues reading Rust code, but find Go to look like utter spaghetti on the screen.


I have to side with those saying Go has greater readability, and is easier to use and learn. I know there is a strong push (and arguably corporate backed) to put Rust in everybody faces or present it as being superior, but we still need to be realistic and honest. Not saying that Rust doesn't have its places where it can shine, just that a lot of people are going to find Go to be easier to deal with overall, and it was partially designed to be that way.


>I know there is a strong push (and arguably corporate backed) to put Rust in everybody faces or present it as being superior

No part of my comment was doing this, and this bit is entirely unnecessary and adds nothing to the comment you wrote except fan religious wars about languages.


Sure. But just due to Go type system being pretty basic I've never came across Go code I could not immediately grock, not so with Rust. Even bugging some pretty savvy Rust devs for help it sometimes took them a decent amount of time to pin down whats going on.


That’s probably because you’re not used to it. I think it’s common knowledge that Golang is easy to read.


Couldn't the same be said of rust though? The biggest barrier, in my experience, is almost always familiarity with the syntax.


I work mostly with Rust, and have been for years, and no I wouldn't say that Rust is easy to read. It's easier to read than most legacy languages, but it's nothing like Golang.


Pretty much this, yes.

I think my comfort with Rust moreso comes from it feeling like any other C-ish language in appearance. It reads as expected to me.


And much more error prone.


Depends where someone comes from. If they are from a weakly or dynamically typed language (C, python, Javascript) then Go is quite ok. If you're coming from Haskell, OCaml, or Rust, then sure Go might be frightening.


They're all weakly typed languages but C is statically typed.


Python is generally considered quite strongly typed, though dynamic. That is because Python has relatively few implicit conversions, and especially few that are surprising ("1" + 1 is an error, not 2 or "11"; and unsigned short + unsigned short does not equal a signed int).


I think parent didn't say anything to the contrary, or maybe it was edited?


First iteration was didn't include the part about haskell/ocaml.


I've been surprised how writing Rust becomes smooth with experience (mostly; it's still complex in some areas), however, there's more to a language than just the formal part.

Rust requires designing the memory management (ownership/lifetimes) of a program, which Golang doesn't, so even if/when the cognitive load is (hypothetically) equal, there's an additional, non-trivial demand. This is not something that everybody wants in a project (the given tradeoff may not be worth); for sure, if I had to introduce a language in my team only for tooling purposes (ie. small programs), Rust would not be the most productive choice IMO.


That's where I gravitate recently. I got a few more gray hairs learning and doing async Rust and I love the resulting program's speed and correctness.

But it's not easy to quickly prototype stuff with. Just recently I had to write no less than 7 small prototypes and I gave up on the second one, relearned Golang -- took me an hour -- and finished 3 prototypes in a day.

I tend to go all the way in languages so I can use them freely afterwards. But... In Rust's case I just can't justify the effort. Golang really helps you start off a project faster.

I'll still 100% Rust. I'm working on it every day. But indeed, let's use languages where they are at their best.


Anecdote:

I have had year+ long breaks between writing Rust applications. I have been able to get back into it without issue very quickly. At least one of those times I successfully completed a large, sweeping refactoring of a project of mine that had lay untouched for three years. I can confidently say it would have been extremely difficult to do that same task in other languages I consider myself very proficient in and use daily.


> I do wonder how many people who understand Go's limitations (and work around them as you point out above) have truly given Rust a try (takes a few months - can't be done faster)

This is basically the "Anyone who doesn't love my favorite movie hasn't watched it enough times" argument. Programming language design is a complex space, there are no "correct" opinions.


No, I don't thinkbit is that argument. I think the argument is "Rust has a really steep learning curve". Nor do they imply that everyone who has given Rust 3 months will like it.


I tend to agree with his argument though, I can see how a lot of Golang developers would really benefit and enjoy migrating (I’m one of them) but I think it also depends on the type of applications that you usually write.


For me error handling adds a significant distraction due to high vertical space consumption leading to a lot of extra scrolling.

I wished Go formatter would compress

    if err {
        return err
    }
to

    if err { return err }
Even better would be to extend the syntax to allow

   if err return err
But I am Ok with braces.


Two comments:

There are two reasons to have match: exhaustiveness and destructuring/pattern matching. I don't think there's an if/else parallel to `match result { Ok(t) => { ... }, Err(e) => { ... } }`

The fact that a `nil` value can be made useful (which, AFAICT, means it won't blow up your program?) is not a good enough reason to include them in the language. Rust can do perfectly useful things with, say, `Option<Vec<T>>`, and it's possible to distinguish between `Some(vec![])` and `None`. If you care to ignore that distinction you're free to do `maybe_vec.map(|v| v.len()).unwrap_or_default()`. But IMO it's much better to tell the compiler to unify different cases (`None` and `Some(empty)`) than to have to work around the compiler's unification of those cases when you don't want it. It's always easier to increase entropy than to decrease it. (P.S.: should the sum of a nil list be 0, or NaN, or...?)


The problem isn't null/nil as such, it's when the type system treats null values as legal at compile-time.

But if you look at e.g. Kotlin, or C# with "#nullable enable", it tracks whether a given reference can be null or not. So you write if-else code instead of match, but you have to do that in order to actually do something with a reference.


> The fact that a `nil` value can be made useful (which, AFAICT, means it won't blow up your program?) is not a good enough reason to include them in the language.

"Nil is useful", in Go, means that for expensive objects it's often appropriate to pass nil instead of an empty or defaulted object, and call methods on it that act as if it was an empty object or with some default/no-op behavior. This is most obvious with lists/maps but can be applied to all concrete types.

While I can imagine a language in which `Option<T>` can re-export some of `T`'s method set with a default behavior, in practice I don't know any which actually do this.


> The phenomenon I notice in languages that do have it, is the majority of uses cases seem to be a pattern match with two outcomes, one a Some() and the other a None(). It really seems like a more annoying way to write if {} else {}.

In Rust you write this type of thing as:

  if let Some(name) = order.get_name() {
   println!("Order ready for {name}!");
  } else {
   println!("Order number {} ready!", order.num());
  }
So, it's a destructuring pattern match just written as an if-else. Because we did the match here, we can't forget and end up using name when there wasn't one, the variable only exists when it's bound.


If I may ask, I'm curious about why you wouldn't use it to build a User Interface?


One is Go's module/import system. It's pretty inflexible and the connection between exported functions and types and how they are Capitalized or not does create friction for me when I'm "building up" code bases from scratch. Puzzling about module boundaries and type definitions feels like it would consume more time than it's worth in a UI oriented program.

The other is most UIs are wrappers around other functionality, often in libraries. Working on anything other than pure Go code bases adds additional friction, and dynamic libraries add to that complication considerably.


On the other hand, Go's defining feature: Its CSP model, lifted from Newsqueak, was originally designed for user interfaces.

I believe Go could be a really great fit for UIs, but likely never will be as there is little economic incentive to put in the incredible heavy lifting required to build up the support libraries. There are certainly some small projects trying, but without massive resources backing it, it is a struggle to achieve the full fit and finish that we've come to expect.


We always make this discussion with friends. I don't think every language should tick the boxes the same way.

I also personally like Go a lot. It's filling the gap between C++ and Python for me. If I need something compiled with proper threading support, but C++ would be an overkill, I reach for Go.

Go is designed with a human centric view, IMHO: "Make writing great programs easier rather than design a language with novel/cutting edge features, but with a high cognitive load", and I find it as a noble aim as Rust's guarantees and aspirations.

I understand why people love Rust, but I don't think it's the only one, or the proverbial silver bullet that we finally achieved. Yes, it's nice. Yes, it brings many things to the table, but it's not the final solution for once and for all.

You like Rust? Great, Go for it (unintended pun alert), but I think we need all languages from all paradigms and perspectives.

I find this talk [0] very informative and useful while interacting with other languages and communities. I'm not sharing this with any insinuations towards you, but just find it generally perspective broadening.

[0]: https://www.youtube.com/watch?v=YX3iRjKj7C0 - What Killed Smalltalk could Kill Ruby.


> Go is designed with a human centric view, IMHO

If it were Go's design philosophy, it would have allowed unused variables/imports. Those restrictions are there exactly because they help computers, reducing compilation time. The over-focus of compilation time also stems from monorepos being used by Google, whose purpose is also helping computers.


Fast compilation times are a human-centric goal. The machine couldn't care less if it takes a millennia to compile your program. Only humans care about fast compile times.

But, I'm not sure that is even the primary benefit. The primary benefit is that you won't be incentivized to leave unused imports and variables to litter your code, as I see happen all the time in languages that aren't so strict. The Go team has even stated that they decided there would be no warnings because they've learned that warnings get ignored.


they could have had a flag for people who prefer those issues to be warnings. for me, they slow my debugging down a great deal


Then most (if not all) Go programs wouldn’t compile without that flag.

If something can be abused, it’s going to be abused.


I think it goes both ways. Yes, eliminating unused variables and imports accelerates compilation. Also, compilation speed is a great deal for Go, but keeping the code devoid of unused variables also reduces the cognitive load of humans a lot. Keeping language simple is another feature which helps both ways.


> Go is designed with a human centric view, IMHO: "Make writing great programs easier rather than design a language with novel/cutting edge features, but with a high cognitive load", and I find it as a noble aim as Rust's guarantees and aspirations.

I don’t understand his horrid informal writing style trend. You are clearly not quoting anyone and just providing your own interpretation. So why in the hell are you using quotation marks? In this case you could just ditch the quotation marks altogether since the colon already acts as a separator.


Equally, I don’t understand the horrid assumption about everyone being born in an English speaking country and has English as the native language.

So, why in the hell are you just berating me via a comment box?

I made a mistake, alright, and used quotation marks as a tone modifier, because I know no other way to do that.

However in this case you could just pointed me the right direction without berating, since pointing out the mistake already acts as a kind direction arrow.


> Equally, I don’t understand the horrid assumption about everyone being born in an English speaking country and has English as the native language.

Did I even hint that I was assuming anything about native vs. non-native English speaker? Or blaming non-native speakers? No. Play that wounded ESL violin somewhere else.

> I made a mistake, alright, ...

I said that I think that it is bad style. Alright. I didn’t say that it is wrong. (People are doing it a lot these days so apparently it isn’t wrong.)


There's no blame, I agree, but expecting everyone to know proper use of quotation marks (incl. uncommon ways) pulls in some assumption about proper grammar, hence some long experience with the language. Quotation marks are simple tools, but they are hard to master, because they have nuanced uses too. Getting these things wrong is easy.

> Play that wounded ESL violin somewhere else.

I'm not playing a wounded violin, or any violin for that matter. I'm a double bassist. Jokes aside, I'm not being apologetic, just explaining my position. On the other hand, if reading a comment squarely in your own tone bothers you, you might want to think on that, at least a little.

Your wording of your comment implies that I'm doing something wrong by using quotation marks, and I take a note for that. Why are you so upset because somebody admits that there's a room for improvement?

The only thing I'm not agreeing on is your way of sending the message to this shore, that's it.

The thing is, neither English punctuation, nor Hacker News has many facilities to convey tone while discussing. I like to do that in my writing style to convey feelings and tone of my comment. Quotation marks seemed like a usable way to do that, and I possibly got used to that from literary works I've read, so I copied the method.

However, if a native (or more knowledgeable) person tells me that I'm wrong, I tend to believe them and try to learn the proper way, that's it.


> needs better error handling

First it would need to add error handling before it could look to improve upon it.

I'm not entirely convinced it should. I spend my days in a variety of other languages that have added error handling in various ways and, in my experience, it always ends up making errors unnecessarily difficult to do deal with. I regularly wish the idioms of those languages recognized errors as being core to your application as any other value, not something to treat differently. Go really got things right for the type of software I write.

But not all software is solving the same problems. There are a lot of programs where you don't need to think about errors; where stopping the world is fine if you encounter one. Go is not at all a good fit for these situations. However, I think it is okay for Go to not try to be all things to all people. We already have plenty of other good languages that serve other niches. Right tool for the job and all that.


Just make it so:

varFoo, err := GetFoo()

if err != nil {

    return err
}

Can be written as:

varFoo := GetFoo()?

Just like Rust, everyone would stop complaining about Go error handling. But they have this absolutist position on syntactic sugar, even for something like this that would make the language that much nicer to look at and work with.


While that may look good for a hypothetical example, I'm not sure how beneficial it is in real-world use. If your higher level functions are directly passing errors from your lower level functions then you start to bake implementation details into your abstractions which becomes a nightmare later when your implementation changes and callers are depending on those details. In reality, you need to deal with the error immediately and, if there is no better option, return your own error that describes the problem in a non-implementation-specific way.

It is possible that with other error-related features added to the language you could avoid those traps, but Go doesn't feature those either, so simply adding that construct without thinking about the problem much more deeply doesn't buy you much.

If you are solving a stop the world when you encounter an error-type problem that might be okay, although I'd argue that you may as well panic instead. But, again, Go isn't designed for those problems and I'm not sure it needs to be. There are already plenty of good languages designed for that type of work.


All of these things are better handled by good old exceptions (with optional checking, even). All in all, handling errors is often not possible locally -- there is no reasonable thing to do with a db connection error for example at its immediate caller.

The reasonable thing is perhaps to log it there at most, and bubble it up (possibly wrapped as you mentioned). It can be handled for example by a request handler, by returning a 50_ error.


Casting errors to exceptions (panic/recover) are considered a valid approach in Go within your application logic. Its built-in HTTP handlers will even 500 out of the box if a panic isn't recovered beforehand.

But other layers of abstraction lose their utility value if they start to make assumptions about the caller. Maybe in your web service a 50x stop the world error is all you'll ever need if there is a database error, but the next guy using the code for another purpose could have very different requirements and when you have to actually deal with errors, exceptions become a royal pain very quickly. As such, the official line is simply that you shouldn't let errors casted to exceptions cross package boundaries.

But, again, Go isn't really designed for stop the world programming and it's okay to use another tool if your problem space is suited to stopping the world.


I don’t get what you mean by stopping the world. An exception (in most languages) only interrupts the executing thread.


By the way, Vlang (https://github.com/vlang/v/blob/master/doc/docs.md), uses "?" as well with error handling. To include it had/has a number of Go wishlist features.

The nature and culture of Go makes it harder to change direction and be as amenable, but the merits of such conservatism is a "depends" type of thing and can mean incorporating things many years after it has become fashionable or accepted.


So if GetFoo returned a non-nil error, would the function abort and immediately return the error?

I'm used to a question mark being around null handling, but you know, JVM languages lol, null is thought about a lot.


Yeah exactly, it makes everything so much more ergonomic, especially with combinators, as in chaining method calls that might return an error


This has been used for ages, goddammit. The question mark idea kinda stops half way. In most of languages, the question mark is always there, implicitly. You have to add ugly code to say "this failure doesn't require bubbling up".

Go is an experiment in the exactly opposite direction: language puts pressure to really handle the error in a meaningful way every time. And if you bubble up, at least describe the situation a little bit better. Or explicitly give up and receive a penalty for that: the `return err` is nothing to be proud of, it's just a visual penalty for _lack_ of error handling.

I'm not saying it's better in every project, I'm saying it's valuable on some projects.


>There are a lot of programs where you don't need to think about errors

What kind of utopian programming job do you have that you don't have to think about errors? An error is not limited to technical issues like packet loss or unable to open socket. Its also "client A attempted to purchase item B which is limited to client C". How do you express this in Go?

I have no idea why people are so opposed to ADTs. Its like sliced bread with butter, or whatever the phrase is. Its not _that_ complicated...is it?


> What kind of utopian programming job do you have that you don't have to think about errors?

My job primarily requires thinking about errors. It is why I wish for Go-style errors in the languages I use.

But on rare occasions I write things like batch scripts, automations, etc. in which failure means addressing the issue in realtime and trying again. There you don't care much about errors other than ensuring that the world stops when an error occurs to allow you to fix the problem before continuing.

While contrived, if you run into a "client A attempted to purchase item B which is limited to client C" error in this space you're probably going to have to phone them up and tell them that you can't process the transaction, apologize for the mistake, remove record of their purchase, and then once complete run the script again. The program crashing with an error message is sufficient here.

Different tools for different jobs.


I like the reasoning behind Go's approach, but the `if err != nil` ends up polluting codebases like Java's checked exceptions did.

I'm not sure what the better way to do it is tbh.


Java's checked exception's could be handled at a single place by a catch, or just marked as `throws`. It was never as verbose as go is.


Maybe, but then, when Jackson is throwing an IOException because of bad JSON, and the file you opened that had the JSON could also throw an IOException, then it's not just catch it at a single place, as you'd generally handle both differently.


> but the `if err != nil` ends up polluting codebases

Pollute implies that it is unwanted, but this is something you do want. It is the most interesting and important part of your application logic. You want it up front and centre for all to see.

I think we all understand the human desire to want to believe that bad things won't happen, but when one becomes an engineer they have to set those emotions aside and realize that bad things will happen and that your job is to make sure that when bad things do happen that the failsafes achieve an acceptable outcome.

> I'm not sure what the better way to do it is tbh.

I'm not sure any better is fundamentally possible within an engineering context. The vast majority of the job is in understanding the failure modes and being able to communicate to the computer how to gracefully deal with the problems when they occur. As such, it stands to reason that the vast majority of the code is going to be related to errors.

If you are programming for hobby/learning purposes, where when bad things happen your program can simply crash with no consequence, there are different approaches that work well, but Go is decidedly not designed for this space. And, frankly, doesn't need to be as there are already plenty of languages designed for that. Go is, quite explicitly, meant to be an engineering language.


I can't agree with that at all. Checked exceptions can all be bubbled up to a single catch if that's how you want to handle it. That hardly pollutes a codebase.


Sure. But the bubbling of checked exceptions adds a lot of "foo() throws DeepUnderlyingException", all the way up to your single catch.

And the worst part is when reading the code in such a situation, it can get very tricky to figure out exactly where the exception is thrown.

There's a reason that the JVM ecosystem has largely moved away from checked exceptions.


This is pretty much exactly how I feel. I have great respect for the Go team. I broadly agree with their design philosophy. I think CSP is a great theory for concurrency. And I just don’t really like writing Go. I don’t hate it, but I feel like I have to either fight the language or just repeat myself an awful lot.

Honestly, I suspect the deficiency is on my end. Perhaps I’ve just spent too much time with Lisp and have experienced some professional deformation.


It's OK to have ergonomic opinions about languages! In scriptingland, I find Python abhorrent. Ruby fits my brain like a glove (the core language, not Rails). Doesn't indicate anything about the quality of the languages or their respective partisans.


Look at you guys using nice looking languages! Haha, Java got better, but oh god it’s still verbose.

We got rid of the getter setter silliness to be in “constructor land.” ( immutable records from Java 17 )

Function are still not first class citizens, but … they have a working visa now. You can shove lambda anywhere.

It’s still awkward to write functional code

Meh. Recently I’ve been really productive with it and I like the sheer boredom and lack of surprises.


Java is several times more expressive and less verbose than Go, objectively.


Most developers on the JVM with any freedom have switched to Kotlin.


Better check those numbers, only those forced by Mountain View minions have done so.


I'm talking about on the server, not Android... but sure. Bury your head in the sand.


I was free I would not develop corporate software. But oh well


citation needed


Any and all language trend survey results from the past couple of years.


I sorely wish CSP was enforced in any way by Go, but it's not. The only thing which arguably makes Go more "CSP-y" than, say, C++ is that it comes with a decent built-in concurrent queue and some syntax sugar. Goroutines have full mutable access to all the variables in their scope, and lots of Go code ends up being your good old mutex hell -- just more of it, since threads are so easy to create.

I think something way more radical than "C++ threads but with a built-in concurrent queue" is needed. If the default was to share nothing, and any shared memory had to be explicitly shared somehow, that would be a great step in the right direction. Maybe the compiler could even check that explicitly-shared variables are protected by a mutex; something like how Rust mutexes "own" the things they're protecting and you can't access the underlying object without having the lock.


Yes, that part might benefit from more constraints. There was a HN thread which approached it from a different angle: remove `go x()` and instead enforce mandatory use of WaitGroup/ErrGroup (i.e. a block that spawns them is the same block reaps them, always). I might extrapolate a bit here. https://news.ycombinator.com/item?id=26509986


I don't like _writing_ go, but I also don't hate reading & _debugging_ it.

I love writing lisp, but I hate reading & debugging lisp.


Lisp as practiced nowadays is mostly found in small teams with low turnover. As such, features of the language that necessarily have to scale with larger team sizes and turnover rates are probably just not that important to its current user base.

There's a Go code base that a friend and I first wrote 5 years ago and we incrementally update it as we find small bugs because of a system it interacts with that changes. We usually touch it once every quarter. I've had no trouble re-reading the code base and keeping it up-to-date. I can't say the same for a lot of other code bases I've worked in.


Yup, that's the tradeoff that most people somehow don't get: Go is maybe a bit more awkward and verbose to write than other languages, but it's much easier to read and refactor. My work with Go is (unfortunately) mostly private side projects which I sometimes have to leave for months, so I already learned to appreciate this years ago. Plus I also programmed in Delphi for years, so maybe some of the Pascal-inspired features of Go that programmers accustomed only to C-family languages find alien seem familiar to me.


> In the end I think if someone were to write a slightly simpler version of Rust with the Go runtime it might be pretty neat.

OCaml kind of gives you that, but the dev experience isn't as good as Rust or Go in my opinion. Still, I enjoy it a lot, more than Go or Rust.


Agreed - I really like OCaml. Unfortunately it isn't popular enough to get tons of ecosystem love, so I could never find the libraries I need. I'd be pretty happy if it got more popular. Maybe the new multicore work will help.


Yeah, came here to say the same thing. OCaml is pretty great, and the addition of multicore and algebraic effects will make it even better.

It does have some unique ecosystem elements such as Coq, WhyML, Mirage, etc. But common components could be definitely improved.


What about F#?


Rust is basically what you get if you start with OCaml, remove the GC, and add enough features to the type system to compensate for that without giving up on safety.


Does OCaml matches Go speed though ?


They actually sit right next to each other on programming language benchmarks game. Scroll down to CPU seconds, median and percentiles.

https://benchmarksgame-team.pages.debian.net/benchmarksgame/...


The error handling was a disappointment at first, but in practice it is a lot clearer and more usable than any other kinds of error handling I've tried. It could perhaps do with some syntactic sugar, but it isn't a big issue since IDEs can pick up that slack. After about half a decade of Go I'd say that the error handling is something I've come to like.

"More expressive" in my opinion tends to mean "less readable", but there is some personal bias here. The people I've met during my career who like "more stuff" are usually the younger, less experienced programmers who aren't as concerned about long term viability.

I'm in my fifth decade of programming and my approach is "do more with less". When people want more stuff, for me, it means that I'll be dealing with more cases of people not knowing how all the stuff works. Everyone likes to think they can deal with more cognitive overhead, but mostly people can't.

I'm not sure if pattern matching belongs in a language like Go at all. Admittedly I haven't given it much thought, but it doesn't feel right for what Go aims to be. If you want to do Erlang, do Erlang, but I might be wrong about this.

What's error prone about the for statement? I don't use the C-style for statement all that often as way more than 50% of my for loops are ranging or naked for loops with break/continue/return.

Not having nil tends to lead to having to invent sentinel values all over the place, doesn't it? Is that really better? And since you often end up doing things by value in Go anyway, it isn't like you don't have any choice.


I used to dislike it too, but after being “forced” to use it daily (chose a job thats 100% golang on the backend), you really grow into it. It would be really, really nice to have better error handling and a bit less verbosity all around, but in terms of cost/benefit it still beats any other language I’ve worked with by a fair margin.

To get into it, it might help to start by modifying an existing project, a lot less painful than trying to write something from scratch without being familiar with the idioms.


> That isn't meant as a slight or anything other than simply my opinion.

That's fine, we all have our preferences. I love Python, but I can't stand Ruby. I love Go, but hate C++. I make my living with C#, but won't touch Java.

It's okay. We're not wrong. We're just different.


I agree. It feels a bit like where Java/JVM is at: popular, solid runtime, large community/ecosystem, but Java made some bad choices.

In my opinion: Go needs a Kotlin. First and foremost to do away with implicit nulls (imho the biggest mistake), but here are other things that could be impoved you've already mentioned.


The good thing is that Java is incorporating the proven features of other languages. It has gotten records, pattern matching (better than Kotlin's), and in the latest release, virtual threads with structured concurrency, better than async/await, and also better than golang as it lacks structured concurrency.


But implicit nulls are a big pain point (in both Java and Go). And its very hard to fix for Java (and no plant thus far).


There are solutions like CheckerFramework or Nullaway or other compile time annotation processors.


...and Kotlin :)


Better enums and non-nil types, that would be my wish list for Go. And conditional expressions, if there's one more wish to be granted. There's no need to make it resemble Rust: Go's type system is expressive enough as it is.

> has error prone C-style 'for'

Not really true. It has `for index, elt := range v` to iterate over arrays and maps. It's a pity you can't define your own range, that's true.


Custom ranges will be possible at some point: https://github.com/golang/go/discussions/54245


I totally agree. After working with it for a while I feel like its best use is in writing CLI tools due to the compiler making it so damn easy to produce statically linked binaries for any platform, backed by an incredibly powerful standard library.

My issue for application and web server development are in the language design as it restricts me in my personal quest to write loosely coupled code that is marinated in unit tests - at least when compared to other languages.

That said I enjoy the language, have learned a lot from it, use Go often (previous day job) and have a gopher plushy - but I am just not a die hard loyalist. There are things about the language that blow others out of the water, but there are significant portions that leave a lot to be desired.

What I have noticed is the language design has inconsistencies and there are lots of exceptions built into the language to get around missing features; features which are sometimes filled in later leaving ambiguity around approaches.

For example, Go uses a nominal type system with the famous exception of interfaces with methods that are structurally evaluated. The issue is interfaces only feature structural type evaluation on the top level, anything nested beyond that is nominal.

So an interface that has a method which returns an interface requires an implementing struct to return the _exact_ interface from that same method.

You can accept a struct where an interface is a parameter but you cannot return a struct where an interface is the return type.

This is useful when building type safe dependency containers, something that makes unit testing much easier.

Instead people just overuse `Context`, prop drill it into everything and cast types at some point when trying to get things from it.

Other examples are the return types from functions. Go didn't have generics initially so a Result[T] wasn't possible - but also exceptions were not possible. Tuples were not allowed by the type system but the language makes an exception for the function return types - fair enough.

Without type parameters and a need for generic basic types like `map`, `slice` and `sync.Map`, the language made an exception for the the primitive types - giving them generics however this is now inconsistent with the current implementation of generics.

Usage of `make()` and the confusion brought on by managing "references" also adds a bit of friction to the language.

Generics are inconsistent as well - for instance you can have a struct with a type parameter but not methods - however you can have functions with type parameters that accept a struct as their first parameter.

The tooling is a little underpowered, the test coverage analysis tool doesn't consider branch coverage, only statement coverage so you can have 100% test coverage reported with only 50% truly covered.

Making mocks requires cumbersome type generation - this is something I am okay with but the types generated have terrible assertion capabilities.

But there are phenomenal things from the language. Packages are a great design choice, module management from git is simple and effective, `range` is chefs kiss, the compiler is beautiful.

I love the idea of goroutines but I'm not blown away by channels. They are fancy iterators with dedicated keywords which I find can clutter things up a little - but they were really cool when Go first came out and we were only just starting to think about how to manage asynchronous concurrency.


"You can accept a struct where an interface is a parameter but you cannot return a struct where an interface is the return type."

Possibly I'm misunderstanding the complaint, but that is because an interface is a fat pointer, so one returns a pointer to a struct implementing the interface.

https://go.dev/play/p/YOa6NF7rjPF


It's more this scenario https://go.dev/play/p/S2yQqriYTtm

It's because an interface is structurally evaluated only on variable assignment, but it's not always structurally evaluated which limits its usefulness


Rust has set the bar so high that it’s going to be hard for other languages to justify their use.

If I had to make a bet, I’d say Typescript will take over for everything not performance critical and Rust will fit in every spot that either requires high quality assurance or high performance.


Rust users don't even register as a rounding error in the grand scheme of things. Don't get me wrong, I like Rust _the idea_. But to suggest Rust in it's current form, managed by it's current team, will be anything but a niche language for performance and safety critical corner cases is deluding yourself. The vast, vast, vast majority of programming to this day is still maintaining perfectly functional (but old) C++/Java/C/C# code bases. Anything overthrowing those thrones has to be both easy and better. Rust only fills one of these tasks. Go is far more popular because of the lower cognitive load, comparable speed, and relative ease at which you can become productive. Go won't replace Rust and comparing them is wrong. Go will, however, always be more popular than Rust. At least until Rust is adopted and supported by more major companies, used in major roles, and expands beyond it's niche.


Right, and no one will use Go, Java, Kotlin, Elixir, Ruby, Python, JavaScript, Haskell, OCaML, Ada, Lisp... anymore /s.


This truly is the final, futile iteration on the meme of: I want <current existing language> but with or without X:

> That said, I don't know what I'd want to drop from Rust so maybe I'm just fantasizing.

What is X? I don’t know.


Rust is adding new features at the speed of C++/C# which is quite bad imo, it's good a recipe to have different code base / way of doing things in just couple of years apart. Now for Rust there are many things that could be changed, async etc ...


At first I thought this too, but if you look at the actual "features" they are adding they are all more less just smoothing out existing features. There aren't really any major new ones I can't think of that expand the "surface area" of the language. There is just a lot of polish still needed, esp. to things like async, const, etc. and most of the features appearing are about reducing the burden to use existing features.


Exactly. All of the recent big features were added a few years ago, and now they're really just filling in the gaps.


What exactly has Rust added in recent months or even years? The way I see it most changes in the language are making existing features work more consistently.


V fixes most of these, but it doesn't use Go's runtime:

https://vlang.io/compare#go


I get you want to compare with Go, let have some benchmark. Despite V being new, the difference is quite insignificant for short-lived process in most case. You are more likely use Go for production at this time.

https://programming-language-benchmarks.vercel.app/v-vs-go


Depends on the use case and persons involved. Nevertheless, V is on a constant march towards 1.0, and has been doing so at a higher pace than most other languages.

And, its not necessarily an "either or" or "us versus them" situation, people can use both or whatever else "floats their boat". It's a bit disheartening to see programmers that fall into being so closed-minded or disparaging to other options or something new.


It also doesn’t work :)


You have never used it or are on an unnecessary disrespectful troll crusade. It works very well, for many of its fans, and keeps improving and getting better (at a high constant pace).

Watch, learn, download, and enjoy this V program (https://youtu.be/6H7dprSwr74), among so many other examples (https://github.com/vlang/V/tree/master/examples).


[flagged]


That's complete and uncalled for troll nonsense. V has hundred of releases (https://github.com/vlang/v/releases), with hundreds of contributors to its open-source project (577 and growing). It exists and works.

It's one thing to like some other language, its another thing to spew disinformation, lies, and flames about others.


The V creator makes a lot of promises but most don't pan out. When pressed on them, they often say it's "planned," not actually here, even though their marketing says it is here.

Some good links to read:

https://news.ycombinator.com/item?id=20230351

https://xeiaso.net/blog/v-vaporware-2019-06-23

https://xeiaso.net/blog/OVE-20190623-0001


[flagged]


Look at your comments and post history in the past couple of weeks. You submitted 3 separate posts about V, and your recent comments are just calling people who call out V "trolls" and "spammers." Observe your own bias before you cast others down.


I hope you don't have some mental issue that doesn't allow you to discern differences. To support or like a programming language is not spamming or trolling.

I'm not running around HN posting that "Rust is lies", "Zig is fake", "C++ is dead", etc... To include, not here insulting or trolling the creators of other languages. Lastly, nor am I spamming people to visit my website or troll blogs, that contain resume and Patreon accounts.

If I don't like a programming language, I simply don't use it. Don't need to post hate spam everywhere or tell lies about it. If I like a programming language, I might post about it. And I'm not such a blind evangelist or fanatic, that I only have "one true language" and then crap and troll on all others.


It has an automated release generated every week. The could be no commit activity whatsoever and there would still be 52 "releases" of V every year.

It's one thing to like V but it's another to constantly repeat misleading information in an attempt to give it more credence than it has.

Even V's own changelog only contains 28 releases (mostly from the 0.1 era in 2019 - early 2020) not "hundreds" like you've claimed.

https://github.com/vlang/v/blob/master/CHANGELOG.md


For starters, you are a known troll account. Your very account name, vlang1dot0, was made to harass and troll. That is all that you do on HN.

I was referring to the GitHub releases, which very clearly state they are releases. Each of these Vlang GitHub weekly releases has a compare tag to show what has changed from previous releases.

This is a comparison of changes made between the latest release and the one of 4 weeks ago: https://github.com/vlang/v/compare/weekly.2022.35...weekly.2.... That's 150 commits and 722 files changed. The releases are clearly different, with numerous changes. That's 1 month of changes, if we looked at 3 months, 6 months... We would see even more massive amounts of progression.

Now, back to the FUD you are pushing with your known troll account. The game you are trying to play is to obfuscate the clear as day lie that V is vaporware. Nobody is falling for it. And the more you keep engaging in trolling the V language, the more obvious it becomes to everyone else what you are doing and what your agenda is all about.


Didn't dang already issue you a warning for this perpetual nonsense?

https://news.ycombinator.com/item?id=31930011#31941278


Love go as a platform.. self contained binaries have been a miracle for ops..but have a few big hangups about using the language full time because of the sucky ergonomics.

* No optional/named parameters. Writing a whole function per parameter for function chaining is excessive. This would not be difficult to add to the compiler (i've done it and have seriously considered using the fork) but it seems like the team is just stubborn about adding stuff like this.

* No default struct values. 0 isn't good enough in real world scenarios.. have fun writing BlahInit(...) for everything.

* Would be nice to get the question mark syntax for error handling. Error handling shorthand for if err != nil would also be very welcome.


> No default struct values

I love Go too but this does drive me nuts, especially when parsing JSON and wanting to set sane defaults for missing values. Like, for example, booleans that should default to "true".


For that use case, I think that you can assign your defaults before passing your target struct to the unmarshaller.

The unmarshaller will iterate on the json input and set struct fields when json fields are found. This means that struct fields that don't match the json are ignored, and values you have set before will be left as is.


While true, this approach doesn’t work for nested structs, which are instantiated by the parser.


Write a DefaultFoo function which returns a Foo with default values set. Then unmarshal into that.


I think if Golang would have been invented a couple of years later it def would have had sum types. But then, Rust probably wouldn’t have had its insane tooling that is most likely inspired by golang


It’s not like sum types weren’t known a decade before that. Or generics. They just didn’t care.


These issues were also brought up immediately when the language was first shown, alongside nulls, tuples, error handling, etc…


what imperative language had them?


I doubt it. It seems quite apparent that Thompson just wanted to take another stab at creating the next generation in the B -> NB (New B) -> C -> Go family tree. It would have likely been named D if the name wasn't already taken.

Pike slapped his Newsqueak's CSP paradigm on top and the rest is history.


Go is basically an AOT compiled version of Limbo with the method syntax and UNSAFE package from Oberon-2, and they stopped there.


> I think if Golang would have been invented a couple of years later it def would have had sum types.

How to combine sum types and a precise garbage collector?


They're not mutually exclusive. Scala and Haskell had them for a long time now, and Java got them relatively recently.


How can a precise garbage collector work when it does not know if a value is or is not a pointer?


I agree. I wonder if we'll start to see other languages target the Go platform, like with the JVM.


I'm really grateful for everyone who has made Go possible and continues to make it better. Well done language design, extensive standard library, good tooling, and great documentation is something that's almost impossible to find despite there being a multitude of languages.


I am not a fan of new knobs like this. It reminds me of Java where you actually have to think about -xMx blah blah and setting it is a dark art. I would really prefer if we could somehow confer the memory limit from the container environment to go so this could be set intuitively at the container level without mucking about in the go GC internals.


You are actually suggesting what Java does in a container environment lol.

https://developers.redhat.com/articles/2022/04/19/java-17-wh...


Maybe now, but in the past you'd have to worry about things like running out of PermGen space. There were definitely more knobs than a typical container environment.


PermGen was removed from Java in 2014, just a year after Docker was released. Please compare modern with modern rather than "the last time I used it".


I did qualify my statement with "in the past." I was pointing out that Java did, in fact, have a lot of annoying knobs to turn. tbrock was advocating against having such knobs as they make deploying a language diffcult. The fact that Java removed one of the annoying knobs doesn't disprove the fact that having such knobs is annoying.


And in the past it was interpreter only.


I'm sure the goal will always be to be as good as humanly possible out of the box. 99.999% of users will probably never have to think about it. But if your mission-critical application just happens to hit a case which the default behavior handles poorly, would you rather: 1) redesign your system and hope that a different design just happens to not hit the same bad case, or 2) tweak a knob until the GC fits your use case?

I think I'd prefer 2. But 1 will always remain an option I suppose.


I agree that getting a suitable default from the container would be a good idea. It probably requires cooperation from the many different containers out there, though. There might sometimes be more than one process per container, too, which implies some kind of flexbox-like layout algorithm to allocate memory within the container.

And on the other hand, it's a simple flag. They added one memory flag in over a decade. I think they're holding the line on complexity pretty well?


Well, what you got is

1. runtime feature to limit memory use

2. a mechanism to configure it

What we just don't have yet is

3. standard way to read container memory limit

Once that comes to existence, the first two were gonna be needed anyway. And now you can experiment with #3 for the various container runtimes.


I have been developing in Go for several years and feel like I have seen most it can offer which is quite a lot for backend/networking systems.

Besides its GC implementation that works very well most of the time, it also has simple but strong debugging tools to investigate how well you are handling memory allocation and CPU usage. Enforcing consistent coding style also makes it very easy to read other people's code and quickly contribute.

My suggestion to others is to avoid prematurely optimizing memory usage (e.g. trying to do zero-copy implementations) and always investigate performance via these profiling tools Go offers (e.g. pprof). Very often Golang's compiler or runtime will automatically optimize code that may not seem to allocate optimally.

There are still downsides but some of them are actively worked on:

1. Generics are still lack-luster (... but good enough to start replacing a lot of boilerplate code and will improve later on)

2. Error handling is still tedious (fortunately it seems to be their next big focus), it should take less code-space and have an option of stack-trace

3. Stdlib logging is too simple, lacks levels and structured-logging-style (currently actively discussed: https://github.com/golang/go/discussions/54763)

4. Low-level UDP networking is weak (soon to be fixed as they accepted this proposal: https://github.com/golang/go/issues/45886 ), this will become more important as we transition to QUIC protocol

5. CGo (C interop / C FFI) is pretty bad performance-wise compared to other languages, it's OK if it is an I/O operation but anything C interop that requires low latency won't be running great

6. Similar to other languages, nowadays there are growing external dependency trees (e.g. I always wanted to use testcontainers-go to make integration tests easier but man, have you seen the dependency tree you will pull in with that pacakge?), solution in my eyes is flattening and standardizing most commonly used packages but Go in particular is very opinionated regarding what is included in stdlib

The above downsides mostly come from the background of creating distributed data pipelines and certain data collectors/parsers. For building API servers, I keep hearing it is a godsend in its simplicity, so your mileage may vary depending on the business domain you work in.

I would be interested to hear other people's experience with Go, however!


And don't forget, the plugin API has some super strong limitations. I wish that was fixed, otherwise platforms "similar to wordpress" can never become a thing on Go


Actually, folks usually just use gRPC or Yaegi in Go. You can write an SDK for your software that abstracts all of that away for the plugin developer.

See Terraform[0], Traefik[1], or OctoSQL[2].

Though I agree plugins would be welcome, especially for performance reasons, though also to be able to compile and load go code into a running go process (JIT-ish).

[0]: https://github.com/hashicorp/terraform

[1]: https://github.com/traefik/traefik

[2]: https://github.com/cube2222/octosql

Disclaimer: author of OctoSQL


Well aware of all that, but as you point out, real plugins would be better.

And to be clear they exist, but you have to compile them with the same go version and if any dependency is shared, they need to be exactly the same version. There are also serious limitations in the sharable code


I have had cases where Terraform would OOM because each Terraform provider instance maps to a fresh new Go process.

Most of the time this approach is not an issue, and it works really well. But you need to be aware that it's best to keep the number of providers in the 0-20 range per root module.


If I was going to try to make something "similar to Wordpress" with Go I would replace plugins with optional packages triggered by build flags (or possibly a light codegen step), not an actual dynamic plugin system. The Wordpress model is solved with plugins in PHP (for reasons that were to some degree bad even at the time), but it's not actually "I want to inject some external code into this existing process environment", it's "I want a bespoke N of M features because some Ms are mutually exclusive or have overhead I can't afford".

If you have a language that has a module ecosystem, compiles fast, and generates a single binary, take advantage of that; don't try to reimplement fragile dynamic linking.


I'd like to point out also that wordpress can install plugin directly from the UI, which is a lot more involved if it requires the whole software to be recompiled (totally different machine too)


There’s no reason that UI can’t kick off a recompile on the backend, nor any reason it has to be a different machine.


Recompiling the whole software is way more expensive than pasting a file on the filesystem, that was my point.


But it’s not so expensive you can’t do it. If you cache it’s probably not even so expensive anyone will notice compared to the equivalent PHP process. And it would be a massive operational, usability, and performance improvement to the actual server.

Otherwise you’re just saying “I can’t write Go like PHP” - no shit, that’s a good thing.


WordPress is an example. For C# videogame bots, the software is delivered as a compiled executable file (no sources) and plugins are sold as DLLs that you can just copy-paste in a directory.

Kerbal Space Program (videogame) is a big compiled executable and plugins are loaded as DLLs from a directory.

I'm not trying to reimpostare anything, I just wish it was available.


I've written plugins in Go. You don't really need to use the plugin package, and you probably shouldn't anyway.

One way I've done it in the past is via sub processes talking via stdin/stdout. Another way I've done it is just via a regular REST API.

You'll never get enough performance for a lot of tasks if you need to pass a lot of data between applications. But depending on your needs it's more than serviceable. The benefit of this is that plugins don't need to be written in Go, as you have a simple interface.

A good example of this is something like LSP. LSP plugins are abundant and each one is written in a different language. VSCode is Typescript, the Go language server is written in Go. Both can speak to eachother fast enough.


Unfortunately, the performance as you point out is not even close. Not only that, but the API is way more limited by the transport protocol, instead of being able to directly use language interfaces. If you write plugins for PHP, they are just as fast as the basic software. If you write plugins for C# (dlls), they are as fast as the language. Writing using grpc has substantial development overhead and performance overhead, so it makes Go a bad choice by default


Plugins for a compiled binary are just plain hard. Changing the code works better with an interpreted language. Usually you see the software implement it's own interpreter or a scripting language on top to handle this.


Plugins for ELF- and Mach-O-based systems aren't any different than regular shared libraries, modulo slightly different symbol precedence. AFAIU, while a quite different architecture, Windows PE linking treats them quite similarly, too.

Perhaps you meant statically compiled binaries? Even then I'm not so sure about that. Right now I'm working on a project that statically compiles a plugin so its various libraries can't leak or be overridden by the loading application, yet which itself can load other plugins. OTOH, this is in C, C++, Objective-C, and Lua (basically C from the perspective of binary linking), all of which have mature linking semantics. (Well, at least this is true for C. Controlling symbol namespace pollution by C++ and Objective-C code requires more complexity than for C, but at least the necessary compiler and linker flags exist and are mature.)

When you use languages or toolchains that don't invest in an ABI, or which make linking too automagic (with no or little recourse for exposing various linking features a platform may offer), then runtime linking is likely to become a major limitation in some solution areas. For example, there's probably no way to coax XCode to compile my plugins properly, without outsourcing some logic to additional scripts. Ultimately a Makefile is likely the best solution--and the one I chose, anticipating these headaches--as it keeps most of the build transparent. Fortunately, XCode seems to just be a wrapper around a bunch of command-line utilities, so you don't actually need XCode at all.


Beyond linking it is also a problem with ABI compatibility of object layouts, etc. It is so much simpler with more dynamic languages.


The C ABI on pretty much any platform defines object layout for structs, which is usually good enough.


The C ABI is nowhere near enough for passing arbitrary Go structs, all you need is a map or a sync.Mutex field and you need to exactly match plugin compilation to caller compilation.

I wish https://pkg.go.dev/plugin was clearly documented as not being a viable 3rd party plugin solution.


It's not enough to pass arbitrary C++ classes, either. The point is that you can still design a fairly expressive API involving structured data within the constraints that the C ABI imposes on you - and that C ABI will guarantee stability. This is how plugins were usually handled in the C++ world, historically speaking.

(There are also higher-level ABIs like COM, but they are usually reducible to the C ABI in practice - e.g. COM can be described entirely in terms of struct and function pointers.)


You can write C plugins, or C-ABI plugins in any language that can do that, and call them from Go just fine. Nobody just considers that a very good way to write Go, or plugins for Go.


I know, I was thinking C# does something like that though, so I was hoping it would be achievable.

The annoying part about the scripting language is that if something is written in Go, you want developers to also work in Go, otherwise the people writing plugins for your software, knowing better the API and use-cases, have a disconnection with the language actually used for the software, losing potential opportunities for contributions.


C# (usually) runs on top of a VM with a JIT, so the linking is effectively always done at runtime.

It's also why it can do things like generic virtual methods - it can compile an instantiation and adjust the vtable at runtime.


It is probably a bit misleading when so many say: 'golang is easy', it is not, it is as difficult as Java or other language, probably easier than c++ and rust, but definitely not an easy language.

it makes great sense for network with concurrency, not so with real time or low resource devices to me.


I always found languages which require you to always remember to manually call destructors to be quite hard, be they C or Go or JavaScript, when the system has lots of moving parts and performance is a concern. A single mistake and you're leaking.


> remember to manually call destructors to be quite hard, be they C or Go or JavaScript

I thought JacaScript doesn’t have destructors (in the memory/resource management sense) or finalizers…


File handle destructor: https://nodejs.org/api/fs.html#filehandleclose

WebSocket's destructor: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/c...

HTTP server's destructor: https://nodejs.org/api/http.html#serverclosecallback

Etc etc etc.

You're right that JS/C/Go don't have language features called destructors, but they have a whole bunch of types with ad-hoc destructor functions which you have to remember to call if you want to avoid leaking resources.


There's a proposal for syntax to help with this in JS, incidentally: https://github.com/tc39/proposal-explicit-resource-managemen...


Go doesn’t require you to call a destructor


Close, e.g., is a destructor in this context.

Defer helps but some true lexical scoping might be nicer.


I found it much easier than java, and a bit harder than python, for reference


What is hard about Java? It is a very small language.


I don't think that's really true, at least not anymore. It's definitely smaller than C++ or Rust, slightly smaller than C#, but larger than Python, JavaScript, Go.


It is def not slightly smaller than C#, C# is a cool language, but it is definitely well ahead on a road towards becoming the next C++. C# is probably the second or third most complex/feature-full language I know of (not sure whether the list goes from C++, Swift, C#, or the last two reversed), while Java is quite down the list.

Not sure about any objective metric on the other languages, though. Tried to look at ANTLR grammar files for each, but grammar is only one part -- language semantics are not included. (E.g. Rust's grammar is slightly smaller than Java's, yet I don't think many would argue that it is the easier one). JavaScript, while easy on the surface, can actually be quite complex/has many non-idiomatic concepts, e.g. `this` handling, property flags, etc. Python has similar "rabbit holes".


All the weird folders nested into folders, the verbosity, the difficulty to follow logic, all the crazy patterns. I guess you also have all the OOP stuff that doesn’t exist in golang. I always felt like its impossible to write java without a beefy IDE


Public static void is already quite a lot for beginners to learn


I don’t know — do you have to know how an engine works to drive a car? While they may not understand at first why they have to write this, I really dislike when people make that 3 words into something impossible to grasp. A good chunk of all programmers have learnt to program by starting with Java. And besides these 3 words, it is a small language (very few keywords), easy, but sufficiently strong type system that will catch most of your problems at compile time, and first and foremost, very well-defined runtime semantics, even when errors happen, all of which help beginners greatly.


One of the underrated difficulties of learning Java is the fact that it demands separate files for each exported class.

Writing and reading simple Go libraries is a breeze. I’ve written tiny libraries that fit in one file. You can read it linearly, starting at the top and following the logical progression of type declarations, functions, globals, etc.

Whereas with an identical Java library, a reader would be presented with several small files and have no idea where to start reading.


If you really need this, you can always expose all your actual classes as public static class members of a single wrapper class:

  public class Wrapper {
    public static class C1 { }
    public static class C2 { }
  }

  //other module:

  var x = new Wrapper.C1();


You could do that, but it doesn’t work with interfaces.

The Go style just lets your code live together for easy reading, no problem. You can comfortably fit all of this into one file: a one-function interface, a couple of small functions that take the interface as a parameter, and two implementations of the interface.

Compare this to 3 or 4 tiny Java files. You’d have to guess which one to click on first.


Having worked on even medium sized projects in golang, I started to appreciate why Java requires that. The golang code base was a mess, struct declarations and their implementations littered all over the place, making it hard to follow what's going on. You then need and IDE, and hope that those structs do not accidentally implement some random interface because of structural typing.


Interesting that you mention interfaces. Mostly I like Go, but one of my bugbears is the difficulty of finding out which interfaces are implemented by a type. It's much easier in Java. Java file organisation is more consistent and regular which is a win at scale.


I think the point is that this is hard to learn. Whether it’s necessary to learn is a separate question. I think it’s easier to be an effective dev when you do understand how these things work, and designing a language to make it easier to acquire that understanding is good.


Never really thought of that particular keyword soup when I was first learning it. It wasn’t an issue. Grokking Java-style OO was much harder. I probably still don’t grok it.


Some very impressive wins here. Makes one wonder how much better things are going to get in the next few years. All without needing to change my programs!


I always thought go needed more GC knobs, good that they added this one and the strong reasoning behind it makes sense. Impressive go has got this far with just one knob!


Still yearning for an Ocaml-like language that uses the Go runtime.


I don't like Go, but I think I would use that.

But I'm ignorant: is the Go runtime built in any way to accomodate third party languages? Apart from writing an interepreter in Go, is there a good way to target it?


No it's not. That's why you haven't seen many alternate languages.

Something else is that the semantics of the Go language itself are purposely limited in ways that help build an efficient runtime easily. So even if you could target the Go runtime easily, it would impose limits to what the language can do as I understand.


This is just an assumption, but it might be possible to translate to Go assembly and then use the Go toolchain.

Edit: https://github.com/minio/c2goasm seems to slightly go in that direction.


Go assembly is just a slightly funny version of the target platform's assembly. At that point, you might as well just assemble for the target directly and avoid the quirks of Go's assembler.


I'd settle for the CoffeeScript of Go. Keep most of it, add sum types, expand generics, and replace nil with options. If you're feeling spicy, make whitespace significant.




I don't see the point, just use OCaml.


I don't understand how this is a response to every "I wish I had blank but like ocaml". Threads exist.


As does OCaml since 1996, no need for sugar substitute instead of using the real deal.

Compiles to native code, has a repl, version 5 is multicore for those not happy with Lwt or multiprocessing in the UNIX classical style of each tool does one thing, and a GC only second to GHC in handling immutable types.


I get that everyone has their own favorite tools, but "use a version that isn't out yet, or use separate processes for parallelism" is a non-answer.

I'm excited for version 5, but until it's out, it's hard for people to take seriously in conversations about _Go_ of all things.


OCaml-on-Go also doesn’t exist…


It is more powerful than Go on every sense, and I really don't get what people think using Go runtime into a completly different language would help.

Maybe they should spend more attention in their compiler design classes regarding runtime implementations and language semantics.


I generally agree about the runtime and language semantics.

However, (stable) OCaml not having multithreading support is still a gigantic limitation. Also, OCaml supports fewer target platforms, and I believe it's worse at cross-compiling (though I admit I may be wrong on this).


> However, (stable) OCaml not having multithreading support is still a gigantic limitation.

OCaml 5 still seems a lot more real and coming-sooner than the imaginary OCaml-on-Go. Sure, it's not ideal that multicore OCaml isn't yet stable, but it does exist, and will become stable.


Absolutely - I was only replying to the question of why you may want this in principle.


My biggest concern would be that Go's runtime is likely not optimized for functional programming.


What happened to microservices and UNIX way, each tool does one thing?

Yes, multithreading support is only in version 5.0, yet for many decades UNIX was multi-process only, and thanks to the latest security exploits, sandboxing with multi-processing alongside IPC seems to be the latest fashion anyway, so no big deal.

OCaml has several backends, including a bytecode one that is used for the REPL and porting purposes.


Doing one thing on multiple threads is very much possible. Threads enable (simple) shared memory multi-processing, which is the most efficient version of multi-processing. You can theoretically get something similar with different processes and memory-mapped files, but multi-threading is much easier in general.

For example, if you want a high-performance web server, it's much more efficient to serve requests through multiple threads (preferably also using efficient IO) than it is to spawn different processes for different requests. I imagine you can coordinate different processes in similar ways, but again, it's much more effort, and either way, those processes are far too intimately tied to each other at that point to call them "different tools".


They also enable plenty of data races and security exploits, as proven in recent years.


Go disliking is due to its syntax not semantics. Go as a dynamic language runtime platform will be interesting; the platform defines the semantics and languages define syntax.

On similar lines, Fable [0] project recently announced rust & dart runtime support making F# a very attractive choice.

[0] https://fable.io/blog/2022/2022-06-06-Snake_Island_alpha.htm...


Go dislike has many different reasons, and some of them are semantics - e.g. the way nil interfaces work, or the dance you have to do to add an item to an array. And then there's the whole issue with FFI, which has nothing to do with syntax, and everything with Go threads being "special".


What? Go’s syntax is great. But it lacks sum types.


I've been toying with adding a new generator to Derw (currently targets JS, TS, Elm, English and Derw itself for formatting) that would support Go-based output. It will probably be an experimental branch later this year. Go's performance and distribution makes it an appealing target to hopefully get a faster runtime (particularly interested in making the compiler faster once self-bootstrapping is finished)


go runtime is not VM like JVM [0]. Go doesn't run on top of runtime, more like run along side it. That's why there's go for embedded where it has no runtime. So it's very less likely other language reuse go runtime.

[0] https://go.dev/doc/faq#runtime


There are also bare metal JVMs for embedded development, here are the two best ones.

https://www.ptc.com/en/products/developer-tools/perc

https://www.aicas.com/wp/products-services/jamaicavm/

And while we are at it, for .NET as well,

https://www.wildernesslabs.co/


There is absolutely no real difference between the two. One just bundles the code with the runtime while the other doesn’t and uses an intermediary format.


I would wager there are far more embedded devices running Java than Go. Though, to be fair, it's usually a stripped-down version of Java.


Go doesn't have bytecode interpreter or JIT, but it still needs a VM, if only for green threads.


Not every runtime environment is a virtual machine...


That's true, but I would argue that any runtime with custom green threads is.


Would you consider the C runtime a virtual machine once you link against libdill or use OpenMP? If not, why not? If so, well, then we just disagree on the term 'virtual machine'. My litmus test would be the presence of some sort of well-defined instruction set.


A quick look at libdill indicates that concurrency there is cooperative, so it's not green threads, either. So, that's just a library.

OpenMP uses OS threads, doesn't it?

When you have coroutines that can be preempted, without explicit yielding in the source code, that's the line at which I would consider it a VM. Basically, it's a VM if some userspace code (JIT, GC, scheduler etc) runs in the background and does things to your code.

I don't think there's a definitive interpretation of VM, so this is all arguable. But e.g. Java and C# are generally considered VM languages even if AOT-compiled, despite the fact that there's no bytecode involved past that point.


Historically, goroutines worked like in libdill, only passing control to the scheduler on certain calls. So did the Go runtime suddenly become a VM at version 1.14, when preemption was added?

And why the distinction between OS threads and green threads? Sure, in case of OS threads, it's not the language runtime that does the scheduling, but that just means that the operating system is now our virtual machine.

I would argue that what makes a language runtime a virtual machine is not the presence of a garbage collector, a jit compiler, or a scheduler (with or without preemption), but that it has well-defined semantics in terms of something that looks a bit like a real machine - hence the name! In particular, there's a set of instructions it understands. In case of the JVM, the instruction set is defined in the Java Virtual Machine Specification (with Java bytecode its representation), in case of the CLR, it's defined in the Common Language Infrastructure specification (with CIL bytecode its representation), in case of Smalltalk, it's defined in the Blue Book, in case of WASM, in the WebAssembly core specification.


Then you can sure list some differences between go's and JVM's runtime.


Most relevant in context of our discussion: The JVM is a (virtual) stack machine with its own instruction set and semantics specified in the Java Virtual Machine Specification. As far as I'm aware, something equivalent does not exist for Go, or as an internal implementation detail at best (some intermediate representation might potentially qualify if you squint at it the right way).


That's also an implementation detail, because the majority of execution time happens in native code with a native GC "managing it" the exact same way, not by interpreting abstract virtual machine instructions.


It's not an implementation detail, the interface is mandatory and public (Java bytecode).


F# on Go, yeah would be killer! Some time when I have enough free time, if it doesn't exist already, I'd love to build.


what would it give that evolution of the .NET runtime and tooling isn't likely to offer?


Go has nice libraries for networking on Linux.


Nicer than .net core?


Why would that be a good, over much more performant runtimes like the CLR, JVM, etc?


Why not ocaml?


Not trying to be inflammatory but has the pclntab taking ~30% for binary size been fixed yet? IMO it's a pretty severe design choice.


> IMO it's a pretty severe design choice.

I don't think that's true, otherwise it would've been fixed already. For the main use case of Go (https://go.dev/blog/survey2022-q2-results), APIs and web services it just doesn't matter if the binary is 1MB or 30MB. Unless you are working on some embedded systems where space is scarce I don't see it as a big issue.


1MB vs. 30MB is the difference between:

- Pull the prod image to my laptop in ~1 sec vs. pull the image in 10ish seconds

- CI build and push the image instantly vs. ~2-3+ seconds

- Long-term (1y+) retention of per-pipeline/push artifacts vs. per-branch/tag artifacts.

- All images in each node's cache vs. 50% of images in each node's cache.

Yeah, I don't really give a shit about the 30MB once it's out there. But there's also all the steps to get it / keep it out there.


No one really care about binary size increase , especially because you can run a Go binary in a docker image with 0 dependencies ( scratch image ) which has a very small size.


That's not true. I've warmed up a lot to Go over the last 6 months. I enjoy writing it and I find it useful. However, it still frustrates me that the base binary size is something like 15 MB. That being said, if you add a bunch of static linking into a C++ or Rust app the sizes may be comparable. But because Go frontloads so much stuff with it's forced static linking the binary sizes can become unruly.

You can imagine transfer costs in a CDN, storage, etc all mattering in the edge cases. Not everyone would have this problem but if your Go app is sufficiently popular and you need to distribute it you may see this as a problem.


do you know how things like tzdata and locale are handled in a single Go executable docker image ?


tzdata, at least, can be embedded in your binary: https://pkg.go.dev/time/tzdata


You can build a much smaller 0 dependency binary in Rust/C so what's the value prop for Go here?


The single biggest silver bullet for provable bug reduction that has ever been invented: GC.

Perhaps the Rust burrow-checker will prove to be the second silver bullet for bug reduction, but for now, the only thing that has ever been invented in programming language design that provably reduces the amount of bugs in an application is having a GC (not types, not getting rid of null values, not monads or other HKTs, not CSP).


The value prop is it's not Rust or C?


Sounds like a sidegrade to be honest, not worth the massive bins


where is that issue listed?



Ah that seems to be the issue some CockroachDB guy complained about. I don't think its high priority for Go team.


The CockroachDB guy didn't understand what he was doing. There's a refute from Russ Cox here: https://news.ycombinator.com/item?id=26834128


Yes, I remember that, didn't want to link RSC's comment to avoid more angry replies here.


[flagged]


> that got my blood boiling. but i know it is only a few bad apples(which I'd be happy to meet in person and give a "talking" to) and has nothing to do with the language itself.

Overreacting much? I hope you don't work with other people


What do you have against BLM?


Not him, but people outside of US don't care about US social and political issues. Even if Go is used 90% by Americans, website for programming language is still not a place to push politics.


I'm not from the US either, but I don't think that asserting that all people should be treated equally is "politics". Actually it's a basic human right that I think most of us agree with - at least in theory, the problems only come when applying it in practice...


So, if people inside the US said that about the plight of people in Europe and various particular countries, would you agree? Somehow I don't think so. Let's not let bias, blind us to important socio-economic or human rights issues.


It sounds like it's more than "don't care", it's a "I'm appalled enough to make a comment"


I'm from New Zealand. There's support for BLM here. It's not like being non-white is a US only issue


My personal opinions aside, what does a programming language has to do with a political movement in one of the 195 countries in this world? Nothing, that's what. The political activists hijacked the Go community they had access to and power over for their own personal beliefs and agenda disregarding every bit of ethics or responsibility they were entrusted with. As the saying goes, with great power comes great responsibility. And they took advantage of that power for their own benefit. That's what i have a problem with. I could care less about BLM, I am not black nor live in or hold a US citizenship. But I have a problem with someone forcefully shoving their own personal beliefs down my throat.


I can understand being frustrated but isn't this somewhat of a very strong reaction to literally just a static element on a webpage? The more likely explanation is that you do care about that ideology in some way


Interesting. So when you see talk about aiding those whose lives were destroyed in the present war in Europe or facing disasters like floods, is that your stance as well, that somebody is pushing another of their causes down your throat?

We can pick and choose, but let's not act like bias is not involved.


Whereas I appreciate their actions. I don't feel that respecting people's human rights is "forcefully shoving their own personal beliefs down my throat"


Nothing against the statement, but the related politics led to this outcome:

https://www.cnn.com/2022/09/25/us/minneapolis-crime-defund-i...

And the organization named BLM didn't live up to its name:

https://www.washingtonexaminer.com/news/blms-millions-go-una...


After being bitten by the overhead of Go’s GC, we stopped using it and went back to time-tested C++.


What version of Go were you working with? As the post shows, things have improved a lot over the years.


Why not Rust?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: