Good-bye core types; Hello Go as we know and love it
324 comments
·March 26, 2025liampulles
chamomeal
I sorta agree. It can be ridiculously hard to understand and maintain other people’s complicate TS generics. But the payoff is that you have a type system that nearly disallows consumers of your interface to use it incorrectly.
Like you’d never get the amazing type safety of libraries like prisma and kysely (an ORM and a query builder) if you couldn’t make such ridiculously expressive generics. But, only a teeny tiny subset of TS devs can work on generics that complex. I definitely can’t!
It’s a tradeoff like anything else.
yencabulator
Meanwhile, KeystoneJS (a Typescript CMS using Prisma ORM) has such complex generics that I am unable to write a field validation function as standalone, only inline; I simply cannot express the type signature! Ridiculously expressive can be just ridiculous.
lenkite
Generics would be far less gnarly if language designers adopted Zig-like comptime instead of designing a second mini-language within the main language.
liampulles
Yes, that's very true. Go tends to be very much leaning on an un-magic approach, so more raw SQL than ORM for example. So as you say I think the two different approaches fit the language ecosystems they are in.
ok_dad
What’s an example complex generic? If you have one, that is, I’m curious what kinds of things people do with types. I’m a fan of using types, myself. Thanks in advance.
steveklabnik
I don't want to scare you... but here's a function from a codebase at work. https://github.com/oxidecomputer/omicron/blob/5fd1c3588a45ad...
That's ninety lines of type constraints.
foresterre
I sometimes see types in TS with complex generics like [1], but instead of being general like in this example, they're more specific to the type of their values.
Couldn't find a proper example on the spot in an open source project.
[1] https://github.com/sindresorhus/type-fest/blob/main/source/g...
chamomeal
Honestly any typescript library with great type safety probably has seriously gnarly generics inside. Here[1] is a random file from kysely, which is surprisingly readable even though kysely's type safety is incredibly good.
But things I encounter in the regular non-library world are usually recursive types that have specific constraints. A couple years ago I had my first foray into typescript generics, and was so stumped that I actually gave up. I was trying to map the type of one nested object to another. This[2] is the stackoverflow post from the legendary jcalz that saved me! Check jcalz's link to the TS playground
[1]https://github.com/kysely-org/kysely/blob/master/src/query-b... [2]https://stackoverflow.com/questions/72461962/is-it-possible-...
Frotag
Common usecase is mapping database schemas to "this is what SELECTs / INSERTs" should contain.
eg https://github.com/jakearchibald/idb
Honestly most complicated types boil down to a handful of concepts (mapped types, conditionals, recursion) with a few tricks for working around sharp corners (deferring evaluation for performance, controlling whether a union distributes).
I've even seen someone solve 8-queens in types using a formal grammar but no clue how that worked. Speaking of, if anyone else has other examples of defining a grammar or ast in typescript, I'd love to see it.
Frotag
Common usecase is mapping database schemas to "this is what SELECTs / INSERTs" should contain.
Honestly most complicated types boil down to a handful of concepts (mapped types, conditionals, recursion) with a few tricks for working around sharp corners (deferring evaluation for performance, controlling whether a union distributes).
I've even seen someone solve 8-queens in types using a formal grammar but no clue how that worked. Speaking of, if anyone else has other examples of defining a grammar or ast in typescript, I'd love to see it.
bobbylarrybobby
Check out the implementations of axum’s Handler trait in Rust. https://docs.rs/axum/0.7.5/axum/handler/trait.Handler.html
Mawr
> It’s a tradeoff like anything else.
Could not disagree harder with the sentiment. Selling your kidneys for $20 is a tradeoff too. You can't just throw "it's a tradeoff" around as a go-to discussion and critical thought ender.
chamomeal
lol I actually laughed out loud at that. I might steal your kidneys comparison for certain conversations at work.
Didn't mean it as a discussion ender, I think it's the type of tradeoff that anybody really needs to think about when they start down the rabbit hole of crafting the perfect generic type. Like for day to day stuff, I agree with the grug brained developer[1].
But if I'm writing something that needs to be used in a super specific way, I think it's worth the extra effort/complexity to ensure that other devs can't use it incorrectly. I'm usually responsible for making smaller components that other devs can build from, and it's amazing what you can communicate through types.
9rx
You assert disagreement by repeating what was said using different words? Interesting.
VirusNewbie
>generic functions defined on types cannot use generic parameters that aren't defined on that type, for example
This is bonkers to me, why???
int_19h
For the same reason why C++ doesn't have virtual function templates. The usual way to implement dynamically dispatched method calls efficiently is with vtables, but if your method is itself generic, and it can be dispatched through a superclass reference (or interface value in Go), how do you instantiate the implementation and allocate slots in vtable?
C# can do this because it has a JIT and can compile such methods dynamically (and rejig vtable if needed). In an AOT-compiled language like Go, it would either need to treat this pessimistically and instantiate every possible implementation of every method that could be virtually dispatched anywhere in the program, or else it needs to do what Swift does and generate code that is generic at runtime - i.e. pass some kind of type descriptor with information like size of type and everything else that's needed to handle it, and then the code would look at that type descriptor and do the right thing; this works, but it's non-trivial, and generated code is very slow.
neonsunset
JIT is not a requirement for this and 'GetFoo<T>().Bar<U>()` works just fine under NativeAOT (even if Foo is abstract and Bar is virtual).
Indeed this involves expensive lookup for generic virtual methods. It is also not very friendly to NAOT's binary size once you start having many generic instantiations of the type with such method(s). In the case of Go, I'd assume it will require making both its VM and code reachability analysis more complex to make it work, and they decided to simply shift the responsibility and ceremony onto the programmer which is the standard in Go design.
lenkite
Yet C++ is a very flexible language. You have the tools to implement approaches like "stateful metaprogramming" to give you virtual function templates if you really need them.
There is nothing (wrt language) that cannot be done with C++.
maleldil
Because they chose a half-assed way to implement generics, and the path they took doesn't interact well with the rest of the type system.
jeremyjh
Is it clear they had any better choice after shipping 1.0 without generics?
zeeboo
singron
This is a good write-up of the issue. To see where this craziness could have led, see the C++ overload resolution logic, which isn't the exact same problem but does smell the same: https://en.cppreference.com/w/cpp/language/overload_resoluti...
wseqyrku
I feel there's a clear alternative to the problem presented. I do not expect Empty to match any of those interfaces. A generic method should only match with a generic method of the same arity.
xg15
Just to understand, methods cannot declare any new type parameters of their own, but they can use type parameters from the type they are bound to, can they?
masklinn
Yes.
wruza
I wonder instead why it should feel extraordinary. Methods are tightly coupled with classes, so allowing more flex in a method should be non-free in a non-boring way. What reboot man mentioned comes to mind sort of immediately. How would you dispatch that and, more importantly, to hell with this kind of complexity anyway, even ignoring performance costs. It’s more of a swiss army footgun than a feature.
grandempire
There isn’t a single language which has implemented genetics without those expanding to the complexity of C++ generics. It turns out most of the complexity is essential:
- need overloading to support instantiation - need type functions/associated types. For example what type is inside this container? - need operator overloading so you can substitute generic types into assignment expressions. For example notions of equality. - need duck typing or traits to communicate capabilities
The two simplifications which have historically worked are to use dynamic types, or to use code generators.
rowanG077
It depends on what you mean with generics. If it's only parametric polymorphism and ad hoc polymorphism then I would Haskell solves it trivially and very easy to understand. If you mean it to include full "template" metaprogramming or type level programming then I'm not sure.
VirusNewbie
So the idea of type classes are so scary they went with shitty generics?
DeathArrow
>But in a way, I also think the constraints help avoid the overuse of generics. I have seen Java and Typescript projects where developers had way too much fun playing around with the type system, and the resulting code is actually quite unclear.
Have you seen some C++ code where they make heavy use of templating?
vessenes
You could think of the Go dev team's last ten years as trying to find the right balance between features (asked for by the expert devs that use Go) and simplicity (a value that the designers hold, but that most expert devs making feature requests don't care about). Generics always felt to me like this dynamic in a nutshell. Lots of good reasons to prefer generics when you need them, and it feels like a nearly ecosystem-killing amount of complexity to implement a type system like Rust on top of Go - there's literally almost no reason to use Go in that case.
Anyway, I like seeing this slight reversion in favor of simplicity, I think it's the right call for where Go's targeted: being a better Java for teams of mid-tier engineers.
devuo
It's interesting how simplicity so often gets mistaken for a lack of sophistication. Designing a language for clarity and maintainability is a laudable goal, and so is choosing to use one. Chasing complexity, or reaching for the latest trendy language that lets you "express yourself" in ten different ways to do the same thing, isn't what makes someone an S-tier engineer. Simplicity isn't a concession. It's a hard discipline.
rob74
> Designing a language for clarity and maintainability is a laudable goal, and so is choosing to use one. Chasing complexity, or reaching for the latest trendy language that lets you "express yourself" in ten different ways to do the same thing, isn't what makes someone an S-tier engineer.
But it sure looks good on your resume!
dwattttt
In exactly the same way, complexity you don't understand the purpose of is often disregarded as complexity for complexities sake.
It's easy to get things simple and wrong, and hard to get things simple and right. And sometimes complexity is there for a good reason, but if you don't work hard to understand, you'll fall into the "simple and wrong" camp often.
bb88
One problem with simplicity is that it often moves the complexity elsewhere, particularly if you're designing things that others use.
Brainfuck the language is simple to use and implement. It has only 8 commands. Brainfuck programs are virtually unreadable, unless you can get your head around it.
The advantage of trying to solve complexity is that if you can solve it, you've solved it once and exactly once for everyone involved.
null
elktown
The problem is further exacerbated by needless complexity being seen as sophistication, and in the end; one's competency level. A lot of today's bloat and bullshit is caused by this attitude. Like a football team full of body builders preoccupied with showing of their muscles and wondering why they're languishing at the bottom of the table.
maccard
> there's literally almost no reason to use Go in that case.
I work in C# and C++ day to day now, and in $PREV_JOB I used Go and C++. my go builds on a similar size project were quicker than the linter in my C# project is right now. Go's killer feature IMO is that it's _almost_ scripting level iteration speed.
vessenes
I too like this feature of go quite a lot. But I think it's supercharged by the major reason I'd pick go for a mid-size team; the language is exceedingly easy to parse, code in and understand even if you're not a genius. I think the testing support and very moderate type support hit a valuable place in development ecosystems -- as you say, you get near instant linting feedback about a lot, and it's also pretty easy to read and write in.
Basically, the mental model required for coding in go is low load. That's a great feature.
maccard
> as you say, you get near instant linting feedback about a lot
Sorry, it wasn't clear. I could run `go build && ./myapp` and have my application running quicker than `dotnet format` finishes. Linting in .net is slower than compiling in go.
Agree on everything else. It has it's share of footguns, for sure, but so does every language.
parliament32
Do real organizations actually use C#? Every time we've evaluated it we concluded it's a worse, MS-flavor Java rewrite so we didn't take it too seriously. Does it have any actual advantages over Java?
CharlieDigital
Lots of real orgs use C#.
StackOverflow survey (self reported) for 2024 shows it at #5 leaving out HTML/CSS, and SQL[0]
DevJobsScanner shows it at #4 via scraping job postings[1]
It definitely has heavy adoption; well above Rust and Go despite what we see here on HN.
> Does it have any actual advantages over Java?
The language evolves faster and is more akin to Kotlin than to Java, IMO. The DX is fantastic and there are a few gems like LINQ, Entity Framework, and Roslyn source generators. Modern C# can be very dense yet still highly legible.C# switch expressions with pattern matching (not switch-case), for example[2], are fantastic.
[0] https://survey.stackoverflow.co/2024/technology
[1] https://www.devjobsscanner.com/blog/top-8-most-demanded-prog...
[2] https://timdeschryver.dev/blog/pattern-matching-examples-in-...
WuxiFingerHold
You are overestimating the voices / anecdotes / evangelists from the internet. I did this as well ... so no offense. When reading HN or Reddit one can easily get the impression that Java, C++ and C# are dead and the world has turned to Rust and Go. Esp. C# is underrated in the real world.
Here's some data of 12M scraped job offerings:
https://www.devjobsscanner.com/blog/top-8-most-demanded-prog...
JavaScript: 31.42%
Python: 19.68%
Java: 18.51%
C#: 11.90%
C/C++: 8.29%
Go: 2.38%
Rust: 0.39%
I was a bit shocked about the Rust numbers. I'd expected it to be slightly above Go. Anyway. C# is strong. Java even more.
munificent
When I was at EA, almost all of the internal tools for building and editing games were written in C#.
jeremyjh
It is quite widely deployed in many non-software companies of all sizes that build custom software to run their business, and really seems to dominate in small and medium businesses.
Its also popular in Game dev mostly due to Unity, but you have other good options to use C# in game dev as well. I can't think of another kind of software company that uses it a lot other than ones that integrate deeply into the microsoft stack.
As a language, it was far, far ahead of Java for many years (the long dark tea-time of Java 7).
pjc50
It is IMO better than Java these days, although they're pretty close. Whenever I have to go back to Java I find odd things missing, like unsigned long.
AOT is pretty good when it works.
wseqyrku
> Do real organizations actually use C#?
Sure. For me the best thing about dotnet is that you most likely find an official solution to most "basic" things needed to develop microservices (I intentionally call these basic because you don't want to worry about lots of things at this level). Go on the other hand excels at cross-cut and platform development.
wvenable
C# is easily my favorite language and Java is one of my least favorite languages.
osigurdson
>> Does it have any actual advantages over Java?
Probably not. Why learn C# if you already know Java? Similarly, why learn Java if you already know C#?
LtWorf
I can't trust any programming language where there's data structures I can't implement myself.
jimbokun
Generics really reduced the sets of data structures you can’t implement yourself by a lot.
divan
What data structures can't be implemented without generics?
vessenes
You are not the target market for go then. I personally think using Forth is fun, but not for large projects.
somezero
Not sure simplicity of spec translates into simplicity of user code. This change is backward compatible and will allow things that previously were disallowed.
trentnix
> being a better Java for teams of mid-tier engineers.
That cuts me right to the bone.
eikenberry
Mid-tier engineers are the most dangerous type of engineer as they've learned enough to make decent abstractions and they tend to run with that over-engineering everything they touch.
IMO it is better said that Go is designed as being a good language for Senior and Junior developers, where mid-tiers will probably hate it.
karmakaze
This explains why I lost interest in Go right after it got generics and went back to Java (or Kotlin actually).
I do like to dabble in F# still.
zik
In the end it was a dumb comment by one of the Go devs which got jumped on by all the Go haters. Contrary to the popular meme, Go's not just for mid-tier programmers - it never was (and to be fair to Rob Pike, they've twisted what he said). But sure, it makes it easier for programmers at all levels to get started, and to get real work done. That includes advanced people as well as the inexperienced.
I think the ultimate goal of making a programming language is to cause the least friction for a programmer trying to get real work done, and in my experience Go's great from that point of view. Language bells and whistles may be exciting, but often don't pay their way in terms of real world productivity, IMHO.
null
kubb
Nice! For anyone wondering, Go 1.25 won't be adding any actual language features. It's a minor release.
Maybe we get sum types by 1.30 :)
ashishb
I wish we can have immutable runtime objects (e.g. final in Java or const in C++).
Go has constant (`const`) type that can be evaluated at compile-time but no const type for something that can only be evaluated at Run time.
pie_flavor
Non-deeply-immutable reference fields are a joke and solve no problems. They don't save you from needing to make defensive copies to stop shared mutation, and everything else is a padlock on your left pocket to stop your right hand reaching in.
jcelerier
I can't count how many times adding `const` in c++ saved me from myself, I do t understand why you would ever not want it. You even say it: "padlock on your left pocket to stop your right hand reaching in"
Do you trust yourself to write perfect code 100% of the time ? No? Then padlock it is
drdaeman
I'm afraid that'll only result in some people using those inappropriately then other people using `reflect` to mutate those objects. Just like with "private" methods and fields when library authors create a good library but don't expose something crucial for anther person's task at hand.
XorNot
Man I can't remember whichever black magic I invoked to bust into some C# sealed classes for some office application.
But I was glad it existed because it enabled a whole set of valuable business automation at the time.
umanwizard
Nothing in any programming language I’m aware of prevents you from opening /proc/<pid>/mem and changing the state arbitrarily. I don’t think we should avoid adding features to programming languages because a malicious coworker might work around them.
ajb
There's a proposal[1] by Ian Lance Taylor of the go team, but it includes every sum type implicitly including nil. Which very much not what you'd expect of a sum type, but every interface being zeroable seems to be embedded quite deeply in the language.
masklinn
> every interface being zeroable seems to be embedded quite deeply in the language.
Not just every interface, every single type. And it’s at the very core of the language. For any type T you can name, you can write
var v T
And it’ll give you a v you can interact with, no opt out. You can barely opt out of implicit shallow copies via hacks based around lock method names.thiht
Every proposal they make start with something absurd, gets backslash in the comment, and after a few iterations it ends up with a lovely proposal. I almost suspect they do it on purpose to make sure each significant proposal gets enough engagement
ncruces
Sum types in Go need to have a zero value, if it's not nil, it needs to be something else. There's nothing purposely ridiculous about it.
atombender
I wish the error handling proposal had gone the same way. The numerous error handling proposals finally got an official proposal [1] for a "try" keyword, which was arguably less elegant than some other proposals, and got a lot of pushback, and the Go team eventually withdrew it for somewhat unclear reasons. They admitted the proposal was flawed, but the main reason seems to be that many comments didn't want a solution in the first place ("for many of you it’s simply not a problem to solve").
[1] https://www.infoq.com/news/2019/07/go-try-proposal-rejected/
valenterry
Go repeating the billion dollar mistake. Why am I not surprised...
ajb
Thinking about it, it's not obviously worth making sum types non-nillable unless you also figure out a comprehensive nil-free solution for the rest of the language. Because if you include any interface in your sum, that branch will be nillable anyway. So you might as well check for nil only once at the base case, rather than on each branch.
kubb
Come to think of it, this might be one of the reasons why we don’t have them. There’s no obvious solution without drawbacks, but I believe there are solutions.
karmakaze
For some reason, sum types is something that rarely gets done right.
Most languages settle for something close and users defend their choice/Stockholm syndrome.
int_19h
All they need to do at this point is allow type union interfaces to be used as regular types and not just generic type constraints.
kubb
And modify the switch statement to be able to ensure that all variants are handled.
carlmr
Exactly, since messing around with OCaml/F#/Rust I feel like sum-types + exhaustive match (with deconstruction) is such a powerful construct to prevent logic errors and ensure maintainability, that every language without it feels so limited.
greenavocado
Go doesn't have direct sum types, but we can use interfaces and type switches:
package main
import "fmt"
// Define types for our "sum type"
type Success struct {
Value string
}
type Error struct {
Message string
}
// Interface for our sum type
type Result interface {
isResult()
}
// Implement the interface
func (s Success) isResult() {}
func (e Error) isResult() {}
// Pattern matching using type switch
func handleResult(r Result) string {
switch v := r.(type) {
case Success:
return fmt.Sprintf("Success: %s", v.Value)
case Error:
return fmt.Sprintf("Error: %s", v.Message)
default:
// Go requires a default case, which can help catch new types
panic("Unhandled result type")
}
}
func main() {
result := Success{Value: "Operation completed"}
fmt.Println(handleResult(result))
}
Here is a practical example of it: package main
import (
"fmt"
"time"
)
// Common interface for our "sum type"
type Notification interface {
Send() string
isNotification() // marker method
}
// Email notification
type EmailNotification struct {
To string
Subject string
Body string
}
func (n EmailNotification) Send() string {
return fmt.Sprintf("Email sent to %s with subject '%s'", n.To, n.Subject)
}
func (EmailNotification) isNotification() {}
// SMS notification
type SMSNotification struct {
PhoneNumber string
Message string
}
func (n SMSNotification) Send() string {
return fmt.Sprintf("SMS sent to %s", n.PhoneNumber)
}
func (SMSNotification) isNotification() {}
// Push notification
type PushNotification struct {
DeviceToken string
Title string
Message string
ExpiresAt time.Time
}
func (n PushNotification) Send() string {
return fmt.Sprintf("Push notification sent to device %s", n.DeviceToken)
}
func (PushNotification) isNotification() {}
// Function that handles different notification types
func ProcessNotification(notification Notification) {
// Type switch for pattern matching
switch n := notification.(type) {
case EmailNotification:
fmt.Printf("Processing Email: %s\n", n.Send())
fmt.Printf("Email details - To: %s, Subject: %s\n", n.To, n.Subject)
case SMSNotification:
fmt.Printf("Processing SMS: %s\n", n.Send())
fmt.Printf("SMS length: %d characters\n", len(n.Message))
case PushNotification:
fmt.Printf("Processing Push: %s\n", n.Send())
timeToExpiry := time.Until(n.ExpiresAt)
fmt.Printf("Push expires in: %v\n", timeToExpiry)
default:
// This catches any future notification types that we haven't handled
fmt.Println("Unknown notification type")
}
}
// Function to record notifications in different ways based on type
func LogNotification(notification Notification) string {
timestamp := time.Now().Format(time.RFC3339)
switch n := notification.(type) {
case EmailNotification:
return fmt.Sprintf("[%s] EMAIL: To=%s Subject=%s",
timestamp, n.To, n.Subject)
case SMSNotification:
return fmt.Sprintf("[%s] SMS: To=%s",
timestamp, n.PhoneNumber)
case PushNotification:
return fmt.Sprintf("[%s] PUSH: Device=%s Title=%s ExpiresAt=%s",
timestamp, n.DeviceToken, n.Title, n.ExpiresAt.Format(time.RFC3339))
default:
return fmt.Sprintf("[%s] UNKNOWN notification type", timestamp)
}
}
func main() {
// Create different notification types
email := EmailNotification{
To: "user@example.com",
Subject: "Important Update",
Body: "Hello, this is an important update about your account.",
}
sms := SMSNotification{
PhoneNumber: "+1234567890",
Message: "Your verification code is 123456",
}
push := PushNotification{
DeviceToken: "device-token-abc123",
Title: "New Message",
Message: "You have a new message from a friend",
ExpiresAt: time.Now().Add(24 * time.Hour),
}
// Process notifications
fmt.Println("=== Processing Notifications ===")
ProcessNotification(email)
fmt.Println()
ProcessNotification(sms)
fmt.Println()
ProcessNotification(push)
// Log notifications
fmt.Println("\n=== Logging Notifications ===")
fmt.Println(LogNotification(email))
fmt.Println(LogNotification(sms))
fmt.Println(LogNotification(push))
// We can also store different notification types in a slice
notifications := []Notification{email, sms, push}
fmt.Println("\n=== Processing Notification Queue ===")
for i, notification := range notifications {
fmt.Printf("Item %d: %s\n", i+1, LogNotification(notification))
}
}
The marker method pattern isNotification() prevents other types that happen to have a Send() method from being considered notifications. Tflakes
The problem I have here, is that by being an interface you’ve suddenly made the type nilable. This leads to nasty bugs and segfaults, especially where nil is the default construct for all interfaces.
Ideally sum types would be concrete/non-nilable somehow.
greenavocado
The workaround is even more cancerous but you could wrap it in a struct:
type NotificationWrapper struct {
// This field holds the actual notification data
Value interface{}
}
And use it like: func NewPushNotification(token, title, message string, expires time.Time) NotificationWrapper {
return NotificationWrapper{
Value: PushNotification{
DeviceToken: token,
Title: title,
Message: message,
ExpiresAt: expires,
},
}
}
And destructure it with something like func ProcessNotification(notification NotificationWrapper) string {
switch n := notification.Value.(type) {
Roll it all up in a nice syntax with a preprocessor hahajimbokun
A bit cumbersome, but a nice workaround all the same!
qaq
Honestly that's basically my last item on the go wish list :)
giancarlostoro
I have been following Go since before it even had a Windows build. I love that everything I learned back in 2011 when I finally started experimenting with it, still applies. I never got the opportunity to work with it, so most of my efforts with Go have been small one off projects to learn it.
Only thing that bothered me was hearing an interview with the Go devs where one of the key devs sounded like Generics would never make its way into Go and it put me off the way he seemed so adamantly against such a feature, but now that generics are in I might start doing some of my side projects with Go moving forward just to force myself to become more familiar with Go.
BugsJustFindMe
> Only thing that bothered me was hearing an interview with the Go devs where one of the key devs sounded like Generics would never make its way into Go and it put me off the way he seemed so adamantly against such a feature
Everything about the development trajectory of Go so far indicates that What Is Right And Good at any given moment is largely determined by whatever makes building the compiler easier and not what makes the lives of external developers easier, until the external developers get loud enough about how the language is failing to learn from the mistakes of the past that the internal team relents with an "ok, ok, you win, our bad".
And if I never see another apologist refrain of "You don't need <x>. Just use this code generator to flood your repo with thousands of lines of project-specific-for-no-good-reason boilerplate" again it will be too soon.
throwaway894345
I think most Go devs have been pretty happy with Go’s trajectory. I think it’s mostly the people who aren’t using Go and are unlikely to start using Go no matter its feature set who are the ones who are mostly ignored. It has also been a really good thing that Go “doesn’t learn from the ‘mistakes’ of the past” or else it would have exceptions and monads and lifetimes and inheritance and a Haskell-like syntax.
Go isn’t perfect, but it’s wildly more productive in my experience than any other language, and that matters a lot more to me than being able to be maximally expressive or abstract.
renhanxue
I've coded golang for four years. Web services. I hated it at the start and I hate it even more now. The generics are miserable to use and are very limited in what they can do, the standard library is awful (database/sql in particular is offensively bad, but you have to use it because all the third party tools rely on its interfaces), there's footguns and dangerous syntax subtleties everywhere, and the idea of the zero values for uninitialized struct fields is to me an even worse mistake than the concept of null. At least if you make a mistake with null the program crashes, which is obvious, instead of just silently doing the wrong thing, which is not. The language claims to be simple; it's not, it's just pointlessly restrictive in weird ways wherever it happens to offend Rob Pike's personal sense of aesthetics. At least it compiles quickly, I guess???
I wish we could use some boring language that works, like C# or Kotlin.
maccard
Code generation has a time and a place, but that time and place isn't replacing missing language features.
XorNot
I really wish proper enum types would be added. You always codegen them, but it's the same code everytime.
dimgl
Have you ever used Golang? I have not needed to use codegen once.
BugsJustFindMe
It should be trivial for you to google for go code generation and the cases and reasons for it. That you've personally never done it speaks only to the bounds of the work you've done.
divan
I’ve been using Go since shortly after it got Windows support. I’ve used it across both small and large codebases — for tooling, libraries, fun experiments, serious side projects, and as the main language for backend systems. I even used it on the frontend with GopherJS. Still use it daily.
Since generics were introduced in Go 1.18, I’ve used them exactly zero times. I’ve had zero need for them. I still haven’t encountered generics in any of the real-world code I work with — and honestly, I’ve dreaded the day I’d see something like `func Foo[K comparable, V any, R any, F ~func(K, V) R](m map[K]V, f F) map[K]R {` That’s exactly the kind of code I try to avoid — unless I plan to replace every developer with an AI.
MisterTea
The creators came from Bell Labs and are known to be pragmatic where needed as well as deeply think about a problem and how to solve it correctly. Languages are foundational so getting things right the first time is critical to building a stable ecosystem. Honestly, I am glad they pushed back against the constant demands and gave themselves time to think about the problem.
pas
Go is definitely a step up from C (and from C++ in many important ways), but maybe they could have spent a tiny bit more of that deep thinking about the problem of error handling, so they would have come up with something better than `if err != nil {}` ... no? Just me? Hm, okay.
bee_rider
To be fair, though, error handling is terrible in every language, independent of how much the designers think about it.
9rx
> but maybe they could have spent a tiny bit more of that deep thinking about the problem of error handling
First, to understand how to handle errors differently, you have to understand how errors are different.
Like, is a person's age an error? Your gut reaction is almost certainly "What? No. A person's age isn't an error." Yet soon enough you're writing age verification checks like: if age < 18 { /* not an adult */ }. if age < 21 { /* not old enough to drink in the USA */ } – with all the exact same problems if err != nil {} has. Clearly it is an error in certain contexts.
Keep going and you start to wonder what branching situation isn't error handling. So, really, it seems to me what we really want is a better way to express branching operations. "if" is one of the earliest additions to programming languages, so it stands to reason that it is getting a little long in the tooth.
The effort to improve error handling is clearly there. Core team member Ian Lance Taylor submitted a new proposal and built a reference implementation just within the last few months. There have been ~200 error handling proposals! It is a super hard problem, though. A "tiny bit more" thinking is not sufficient.
burnished
Your IDE may/could collapse those blocks and also allow you to write them from templates. That resolves most peoples problems with error handling since it elides the ergonomics
MisterTea
> `if err != nil {}`
What would you like instead?
umanwizard
And yet, they still somehow ended up with one of the most difficult-to-use modern languages.
Groxx
>If the type of the argument to `close` is a type parameter all types in its type set must be channels with the same element type. It is an error if any of those channels is a receive-only channel.
That doesn't seem to be true - intuitively, element type doesn't seem relevant at all for `close` since it doesn't affect any elements, and it compiles just fine when using a type set that has two different element types: https://go.dev/play/p/IQjTfea9XXy?v=gotip
</nitpick>
Seems like a solid documentation improvement! Hopefully this also helps accelerate some of the flexibility-extensions like shared fields (or a personal hope: "any struct type" plz! it's super useful for strongly encouraging safe habits in high level APIs, currently there's no way to say "should not be a primitive type").
franky47
I’m slowly learning Go, but I’ve got a solid C++ background, is this something like template specialisation, where the compiler can select implementations based on the types passed into a generic class/function?
I wish more languages supported that, we did some crazy things with template metaprogramming.
masklinn
> is this something like template specialisation
No.
> I wish more languages supported that, we did some crazy things with template metaprogramming.
Which is exactly why most langages don’t.
cpufry
[dead]
762236
Now that gen AI can help write code, is a garbage collector necessary anymore?
sunrunner
Sadly, the garbage collector is still needed to deal with the output of the garbage generator ;)
hnlmorg
Thats beautifully put.
I’m almost tempted to print it out and frame it.
jandrese
I'd say it is more necessary than ever given how quickly AIs tend to forget what they've previously written once you get past homework length use cases.
meindnoch
Given the amount of garbage people are producing with LLMs, garbage collectors are more necessary than ever.
LPisGood
I’m curious what you mean by that.
Do you mean that AI can help write perfectly memory safe code and so new languages shouldn’t have a garbage collector?
logicchains
Manual memory management is one of the hardest things for LLMs to get right.
hyperhello
What do LLMs “get right”?
rustc
LeetCode questions. They often give the memorized LeetCode answer even if you slightly modify a question so it has a different correct answer.
cratermoon
Whatever code examples got the most votes on StackOverflow.
eknkc
I seem to use them more and more for repetitive tasks. Where I can write one line and will need to handle a couple more in a similar manner. They work fine.
Also for refactoring they seem to do ok. Things like change this to a function, extract this type etc.
They excel in snippets like “get unique items in this array” or “sort this by property x” kind of stuff where you could easily write or find an answer.
Oh I also like to use them for code review. Not that I’d blindly trust one but you can have another eye to look at your pr (i use claude code for this and love it) and see if you introduced any side effects or missed something.
For anything more complex like having one write some feature from scratch… meh. I haven’t had much luck. Also they seem to fuck up royally in a little complex project if you do not isolate your request like I mentioned above.
__MatrixMan__
I've found them to be pretty darn good at both emacs lisp and matplotlib.
gadflyinyoureye
Flutter widget design.
timewizard
A garbage collector was never "necessary." Automated theft of other peoples copyrighted code is not relevant to this fact.
adhamsalama
Yes.
clownpenis_fart
[dead]
I love and continue to love how seriously the Go team takes breaking spec changes, as well as spec changes in general.
Go generics are a bit of a blip in this to be honest, A) because it is a big change, and B) because it can be difficult to use (generic functions defined on types cannot use generic parameters that aren't defined on that type, for example).
But in a way, I also think the constraints help avoid the overuse of generics. I have seen Java and Typescript projects where developers had way too much fun playing around with the type system, and the resulting code is actually quite unclear.
In conclusion, I pray the Go team strive to and continue to be conservative with the language.