This addresses pretty much all of my least favorite things with writing Go code at work, and I hope--at the very least--the overwhelming positivity (by HN standards -- even considering the typical Rust bias!) of the responses inspires Go maintainers to consider/prioritize some of these features, or renews the authors interest in working on the project (as some have commented, it seems to have gone without activity for a little bit over half a year).
I have to disagree. I'm on record here lamenting Go. I've never really enjoyed writing it. When I've had to use it, I've used it. Lately though, I've found a lot more pleasure. And much of that comes from the fact that it does NOT have all these features. The code I write, is going to look like the code written by most other on my team. There's an idiomatic way to write Go, and it doesn't involve those concepts from other languages. (For better or for worse) So I'm super hyped that we'd have a "compiles TO Go" language, but I'm not as excited as using it as a catalyst to get new (and perhaps wrong for the language) features into Go.
I'm open to alternatives, but I haven't experienced any language constructs that strike as good of a balance between forcing you to handle errors/options when a function indicates it returns them, and allowing you to do so without too much ceremony. I don't think match is enough on it's own, but when combined with if-let/let-else/?-operator I don't feel like I'm sacrificing significant time and effort in dealing with Results/Options, so I'm not encouraged to cut corners and write worse code to avoid returning them in the first place.
The idiomatic way to write Go discourages you from robust error handling; return an opaque error, which callers will probably deal with as a string (or bubble-up as much as possible) because knowing its potential concrete types requires either reading source code or having documentation available (as the idiomatic function return type won't tell you anything about it). The path of least resistance only goes as far as forcing you to acknowledge there might be an error, it doesn't help you make good decisions about how to deal with it.
I think without also having to learn about lifetimes and dealing with async that this still presents an attractive trade-off for people who want to be quickly productive and don't care as much about garbage collection.
I agree with you. I’m not the biggest fan of Go and would personally love these features, but they’re very counter to Go’s purpose. These feel Rust-y to me.
Go was designed to be simple enough that developers can’t write code too complicated for others to read, with a particular eye towards junior devs (and ops/infra people, I think).
This usage of sigils for error handling and union return types is very cool and very expressive, but also going to confuse the shit out of your new devs or infra people. It’s just not a good fit for what Go wants to be.
I’m even sympathetic to the idea that generics are similar, though personally I think the alternative to generics is code generators, which are worse.
Anecdotally, I recently wrote some Go code at work (infra team) that used generics, and I had to look outside my team to even find someone that felt comfortable reviewing generic Go code. I see a fair bit of code using interface{} or any that would be much simpler and better with generics.
A lot of people said the same about generics, and some even still do. I could barely stand Go before generics, and still don't think they go far enough.
From my experience, things I think Go could really benefit from, like I believe it has benefited from generics:
* A way to implement new interfaces for existing types and type constraints for custom types, like `impl Trait for T`. This would obsolete most uses of reflection in the wild, in a way generics alone haven't. This isn't about syntax, it's about an entirely different way to associate methods to types, and with both Go and Rust being "data-oriented" languages, it's weird that Go is so limited in this particular regard. It has many other consequences when combined with generics, such as ...
* Ability to attach receiverless methods to types. Go concrete types may not need associated methods like "constructors", but generic types do, and there's no solution yet. You can provide factory functions everywhere and they infect the whole call graph (though this seems to be "idiomatic"), or make a special method that ignores its receiver and call that on a zero instance of the type, which is more hacky but closer to how free functions can be resolved by type. There's no reason this should be limited to constructors, that's just the easiest example to explain, in Rust associated methods are used for all kinds of things. Speaking of which...
* `cmp.Ordered` for custom types. Come on people. We shouldn't still have this much boilerplate to sort/min/max custom types, especially two full years after generics. The new `slices.SortFunc()` is the closest we've ever come, and it's still not associated with the type. We would basically get this for free if both of the above points were solved, but it's also possible we get something else entirely that solves only ordering and not e.g. construction or serialization.
* Enums, especially if the need for exhaustiveness checking could be balanced with Go's values of making code easy to evolve later. When I need them, I use the `interface Foo { isFoo() }` idiom and accept heap boxing overhead, but even the official `deadcode` analysis tool still to this day does not recognize this idiom or support enough configuration to force it to understand. The Go toolchain could at the very least recognize idioms people are using to work around Go's own limitations.
If we had solutions to these problems, I think most Go folks would find enough value in them that they would still be "Go". In fact, I think people would have an easier time consolidating on a new standard way to do things rather than each come up with their own separate workarounds for it.
This is where I feel "The code I write, is going to look like the code written by most other on my team" the least, because that's only true when a Go idiom has some official status, it's not nearly as true for workarounds that the Go team has not yet chosen to either endorse or obsolete.
> From my experience, things I think Go could really benefit from [...] Enums
Obviously it already benefits from enums.
type E int
const (
A E = iota
B
C
)
Which, once you get past the superficiality of syntax, is identical to, say, what you find in C.
enum E {
A,
B,
C
}
Enums are just a numbering mechanism, after all. No different than hand numbering constants, but without the need to put in the manual effort. Enums kind of suck, though. Are you sure any language actually benefits from them (as compared to better alternatives)?
> especially if the need for exhaustiveness checking
It is true that gc doesn't make any effort to determine how the enums are used, but if it were to it would be a warning like as is seen in many C compilers. As enums are values, not a type, it's not a fault to use them "inappropriately". While it may be all fine and dandy to add such warnings to gc, the Go team has taken a hard line stance that warnings have no place in the compiler. Of course, the external analysis tools like you speak to can still be used to provide these warnings for you. All the information you need is there.
But it seems what you really want is a more expressive type system – specifically sum types. Then you would be able to describe how you expect identifiers to be used without resorting to using generated number values as placeholders. Enums are just a hack to work around a limited type system anyway. If you are going to improve the type system in order to gain improved compilation constraint, you don't need enums anymore.
Rust doesn't have enums and nobody has ever noticed. Hell, many are even somehow convinced it has enums – but it does not. It provides tag-only unions (with, optionally, fully tagged unions) instead. Seemingly that is what you really want in Go as well. And fair enough. It is undeniably the better solution, but at the cost of being more complex to implement.
> the very least--the overwhelming positivity (by HN standards -- even considering the typical Rust bias
As someone who has the "Rust bias", I feel like it's a bit of an open secret that a _lot_ of Rust developers don't actually need the extreme low-level performance that it offers and use it more because of the quality of life things (including some of the features in Borgo, but also tooling like cargo, rustdoc, etc.). I've said for a while that a language that focused on this sort of "developer experience" but used a GC to be a bit more flexible would probably be enough for a large portion of Rust developers, and pretty much everyone Rust developer I've talked to agrees with this (even if they aren't in the portion that would use this).
It's also pretty common for me to see people asking why someone would use a low-level, C++ language for something like web development, and I think the explanation is pretty similar; people like a lot of what Rust has to offer besides the low-level C++-like semantics, but there isn't something higher-level that offers enough of those features yet. Probably the language that would come closest to this is OCaml, but stuff like the documentation and "multiple preludes" are exactly the kind of thing that add friction to getting up and running, which is something Rust has invested a lot of time into reducing.
> I've said for a while that a language that focused on this sort of "developer experience" but used a GC to be a bit more flexible would probably be enough for a large portion of Rust developers
Why then would Go not fit? It prioritizes developer experience (documentation, automatic formatting, etc.) with a GC
Lack of sum types and not much support for a functional programming style. I am totally uninterested in any modern programming languages (e.g. post 1980s) that only allows me to express AND (product types, e.g. records, tuples) but not OR (sum types)
Oh I agree on those but it seemed the person I was replying to was primarily interested in developer experience (tooling, documentation, etc.), not language features. So I was curious about what was missing from the Go developer experience because that's one thing that's generally regarded as a strength
Those are only examples of things that I think would be necessary; I didn't intend for them to be treated as comprehensive. There are a decent number of things in Go that make me feel like my day-to-day experience of working in it is not a priority compared to a design goal of simplicity for its own sake. For example, when debugging code I often will comment and uncomment portions of it as I run it repeatedly to try to narrow down exactly where something unexpected is happening, and having unused variables be a hard error makes this tedious. Is it possible to work around this by manually "using" the variables I comment out in a way that does nothing? Of course. Would it be more "proper" to use a debugger rather than doing something hackish on my own? Possibly! But is this an actual thing that I expect a large number of other developers also do in pretty much every other language without issue? I strongly suspect the answer is yes.
Things like this might be small, but they add up, and at the end of the day, the frustrations that I encounter when writing Rust are smaller and less frequent than the ones I've had writing Go. Obviously stuff like this is subjective, and there's no way to make a language that satisfies everyone. I think there's empirical evidence that there's an audience for a language that fits the niche I describe that Go doesn't fill though.
Yeah, and the common/un-comment workflow isn't just for debugging but also for trying new things out to see what works.
You're right that judging on the engagement of this post and others in the past there must be a big appetite for a language somewhat like Go but with fundamental differences. It's really quite interesting how Go seems to be so polarizing, they really nailed some things and really missed on others.
Yeah, I think would be a good potential candidate if it had a full commitment to support on Linux and Windows, but unless that happens at some point, I don't think I'd consider it over Rust for anything other than Apple-specific development, which isn't something I do
I think, for any language that "targets the Golang runtime", you do need some way to express to the runtime to "use the zero-value default initializer." Otherwise, you'd have no hope of code in your language being able to be used to define e.g. protobuf-compatible types; or of using Golang-runtime reflection (due to e.g. the interface zero-value.)
Some of the design decisions seem to me to be a bit more driven by being Rust-like than addressing Go's thorns though. In particular, using `impl` to define methods on types (https://borgo-lang.github.io/#methods), the new syntax for channels and goroutines (https://borgo-lang.github.io/#channels), and the `zeroValue()` built-in (https://borgo-lang.github.io/#zero-values-and-nil) seem a bit out of place. Overall though, if I had a choice, I would still rather write Borgo by the looks of it.