[HN Gopher] Good-bye core types; Hello Go as we know and love it
___________________________________________________________________
Good-bye core types; Hello Go as we know and love it
Author : ingve
Score : 177 points
Date : 2025-03-26 16:18 UTC (6 hours ago)
(HTM) web link (go.dev)
(TXT) w3m dump (go.dev)
| kubb wrote:
| 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 :)
| qaq wrote:
| Honestly that's basically my last item on the go wish list :)
| ashishb wrote:
| 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.
| blibble wrote:
| final in java only makes the primitive (or reference)
| immutable
|
| not the object
| ashishb wrote:
| Class Rect { final int x, y; //Constructor
| goes here
|
| }
|
| Rect r1 = new Rect(1,2);
|
| Is this not sufficient to create an immutable rectangle
| that I can pass around safely in a multi-threaded code?
| maleldil wrote:
| When x and y are primitives, yes. But if they're objects,
| final only prevents you from re-assigning them. You could
| still call methods that mutate them.
| pie_flavor wrote:
| 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.
| drdaeman wrote:
| 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 wrote:
| 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.
| int_19h wrote:
| 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 wrote:
| And modify the switch statement to be able to ensure that all
| variants are handled.
| carlmr wrote:
| 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 wrote:
| 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. T
| jimbokun wrote:
| A bit cumbersome, but a nice workaround all the same!
| flakes wrote:
| 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 wrote:
| 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 haha
| ajb wrote:
| 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.
|
| [1]https://github.com/golang/go/issues/57644
| thiht wrote:
| 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
| karmakaze wrote:
| 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.
| giancarlostoro wrote:
| 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 wrote:
| > _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.
| maccard wrote:
| Code generation has a time and a place, but that time and
| place isn't replacing missing language features.
| XorNot wrote:
| I really wish proper enum types would be added. You always
| codegen them, but it's the same code everytime.
| therein wrote:
| Same. People go around listing relatively controversial
| improvements but I think we can all agree on this one. It
| is the one that'd make my life easier the most when it
| comes to day to day stuff.
| throwaway894345 wrote:
| 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.
| dimgl wrote:
| Have you ever used Golang? I have not needed to use codegen
| once.
| MisterTea wrote:
| 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 wrote:
| 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 wrote:
| To be fair, though, error handling is terrible in every
| language, independent of how much the designers think about
| it.
| XorNot wrote:
| Seriously. For the amount people glaze Rust on HN, error
| handling when you actually try to use Rust is overtly
| discussed as "oh yeah it's completely broken, most people
| try to replace the whole thing..."
| kibwen wrote:
| Let's not flirt with false equivalences. I've seen
| languages that do error handling worse than Go. I've also
| seen languages that do better.
| MisterTea wrote:
| > `if err != nil {}`
|
| What would you like instead?
| Dylan16807 wrote:
| The most basic thing would be a compact way to say "do
| this call, if there is an error return it, otherwise give
| me the value"
| 9rx wrote:
| That would be the most basic thing, but you'd never be
| able to use it (toy code written for an internet comment
| aside).
|
| 1. Errors quickly lose their usefulness if you simply
| pass them up the stack. Even just on the surface, if you
| don't even know where the error came from, good luck
| making sense of it. Other languages have tried to avoid
| that problem by having things like "sidecar" handlers
| that can handle the error elsewhere, but in the end
| that's just moving code around. You haven't actually
| solved the overhead of needing to do something with the
| error.
|
| 2. Errors simply passed up the stack are, more often than
| not, going to leak implementation details. Consider a
| function that fetches data from a SQL database, where the
| underlying operations produce a "SQLNoResults" error. If
| you let that flow through, callers are going to start to
| rely on it. Now, imagine new requirements dictate that
| you need to fetch the data from an HTTP service instead.
| If you continue to simply pass the error along, now
| callers are going to get a "HTTPNotFound" error instead,
| breaking their usage. Not a good situation.
| Dylan16807 wrote:
| 1. It's not just moving things around. Often you want a
| segment of code to all do the same thing on error.
|
| 2. There are places where you want to abstract your
| errors, but those places are not every function call or
| even most function calls.
| dharmab wrote:
| I would have preferred a Result type.
| burnished wrote:
| 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
| 9rx wrote:
| _> 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.
| umanwizard wrote:
| And yet, they still somehow ended up with one of the most
| difficult-to-use modern languages.
| geodel wrote:
| My thinking is you are better off without Go. There is no way
| to stop Go devs from saying something again you may not like
| and at that point your effort in using Go would be a waste.
| mappu wrote:
| Is any language immune from that?
| 762236 wrote:
| Now that gen AI can help write code, is a garbage collector
| necessary anymore?
| logicchains wrote:
| Manual memory management is one of the hardest things for LLMs
| to get right.
| hyperhello wrote:
| What do LLMs "get right"?
| gadflyinyoureye wrote:
| Flutter widget design.
| cratermoon wrote:
| Whatever code examples got the most votes on StackOverflow.
| rustc wrote:
| LeetCode questions. They often give the memorized LeetCode
| answer even if you slightly modify a question so it has a
| different correct answer.
| echelon wrote:
| Lovable, V0, etc. generate landing sites with slick
| design and easy editability. Startups are going to have
| such an easy time of product demo and marketing sites.
|
| React is quickly falling to LLMs.
|
| Rust, in my personal experience, not so much.
| eknkc wrote:
| 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.
| jandrese wrote:
| 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.
| sunrunner wrote:
| Sadly, the garbage collector is still needed to deal with the
| output of the garbage generator ;)
| hnlmorg wrote:
| Thats beautifully put.
|
| I'm almost tempted to print it out and frame it.
| adhamsalama wrote:
| Yes.
| LPisGood wrote:
| 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?
| meindnoch wrote:
| Given the amount of garbage people are producing with LLMs,
| garbage collectors are more necessary than ever.
| timewizard wrote:
| A garbage collector was never "necessary." Automated theft of
| other peoples copyrighted code is not relevant to this fact.
| liampulles wrote:
| 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.
| VirusNewbie wrote:
| >generic functions defined on types cannot use generic
| parameters that aren't defined on that type, for example
|
| This is bonkers to me, why???
| zeeboo wrote:
| https://go.dev/doc/faq#generic_methods
| singron wrote:
| 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/overlo
| ad_resoluti...
| maleldil wrote:
| 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.
| vessenes wrote:
| 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.
| maccard wrote:
| > 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 wrote:
| 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.
| parliament32 wrote:
| 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?
| pjc50 wrote:
| 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.
| CharlieDigital wrote:
| 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-...
| wseqyrku wrote:
| > 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.
| osigurdson wrote:
| >> 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 wrote:
| I can't trust any programming language where there's data
| structures I can't implement myself.
| jimbokun wrote:
| Generics really reduced the sets of data structures you can't
| implement yourself by a lot.
| divan wrote:
| What data structures can't be implemented without generics?
| bsaul wrote:
| i guess maps and array ? ( if you wanted to reimplement
| your own in a typesafe way, that is)
| vessenes wrote:
| You are not the target market for go then. I personally think
| using Forth is fun, but not for large projects.
| fuzztester wrote:
| examples?
| wwarner wrote:
| anything you can 'range' over :)
| dharmab wrote:
| I have written my own types that can do this?
|
| EDIT: Had to check, yeah I had to implement the iter.Seq
| type to do it
| umanwizard wrote:
| Go added support for custom iterators a few versions ago.
| hnlmorg wrote:
| That's been possible for a couple of releases now
|
| In fact I created a struct that you can range over just a
| couple of days ago.
|
| I can't say I love the way Go implemented it though. But
| it does work.
| devuo wrote:
| 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.
| dwattttt wrote:
| 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.
| trentnix wrote:
| _> being a better Java for teams of mid-tier engineers._
|
| That cuts me right to the bone.
| karmakaze wrote:
| 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.
___________________________________________________________________
(page generated 2025-03-26 23:00 UTC)