24 ms·
Good-bye core types; Hello Go as we know and love it
- kubb 2y agoNice! 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 2y agoHonestly that's basically my last item on the go wish list :)
- ashishb 2y agoI 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 2y agofinal in java only makes the primitive (or reference) immutable not the object
- ashishb 2y agoClass 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 2y agoWhen 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.
- loginatnine 2y agoUnless the object is immutable, like String, Integer, Long, ImmutableCollections, etc. Or your own immutable objects.
- ashishb 2y ago> Unless the object is immutable, like String, Integer, Long, ImmutableCollections, etc. Or your own immutable objects. Exactly. You can have immutable primitives. You can have immutable classes. And you can combine them to form thread-safe immutable classes.
- maleldil 2y agoNow you're restricting yourself to a subset of the language. There's nothing stopping someone from adding a mutable member to your "immutable" type later. It's much better to have immutable bindings/references so that nothing that mutates the object can be done through them. Rust does it very well, for example. Even C++ has a good version of this.
- ashishb 2y ago> It's much better to have immutable bindings/references so that nothing that mutates the object can be done through them. Rust does it very well, for example. Even C++ has a good version of this. Yes, that might be superior but even Java is doing better than Go here.
- pie_flavor 2y agoNon-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 2y agoI 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
- stouset 2y agoI think they’re saying it doesn’t go far enough, but it was hard for me to parse the sentiment.
- Olreich 2y agoConst only saves you most of the time if you have a “const” all the way down the type structure. If I have a type Foo with a field that is a pointer to a mutable value, instantiating a Const Foo just means I’m always pointing at the same mutable value, not that I have an unchangeable Foo. Const support in languages never makes all modifications to data accessed through the variable locked out, just the top level, which makes it much more difficult to ensure that the assumptions about immutability hold without constantly doing deep copies or having to double and triple check that your Const definitions are correct. Const often leads to a false sense of security.
- jcelerier 2y ago> If I have a type Foo with a field that is a pointer to a mutable value, instantiating a Const Foo just means I’m always pointing at the same mutable value, not that I have an unchangeable Foo. yes and that's fine ? for instance how would you encode a graph operation where you want the graph structure to be immutable and the content of the nodes to be variable?
- drdaeman 2y agoI'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 2y agoMan 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.
- School-Cotton 2y agoNothing 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.
- int_19h 2y agoAll 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 2y agoAnd modify the switch statement to be able to ensure that all variants are handled.
- carlmr 2y agoExactly, 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.
- valenterry 2y agoYep, one of the reasons I could never be happy with Go. Union types and type-classes are of similar importance. Any modern language not having them (or an equal powerful feature) just feels sad.
- greenavocado 2y agoGo 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
- ajb 2y agoThere'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 https://github.com/golang/go/issues/57644
- thiht 2y agoEvery 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 2y agoSum 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.
- valenterry 2y agoIt is ridiculous. Nulls were not called "the billion dollar mistake" in Java for no reason.
- int_19h 2y agoThing is, it's not an easy problem to solve while retaining the simplicity. It means that now you need to have rules in the language for definitely-initialized fields (since you can't just default-init them to null as there is no null). Which means that you need full-fledged constructors for everything, and syntax for invoking them in all cases where they may be needed - e.g. think about what should happen if you try to create an array of structs with ref fields in them. This is all solvable - C++ and Rust both do it, for example. But it introduces a lot of complexity to the language.
- 2y ago
- karmakaze 2y agoFor 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 2y agoI 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 2y ago> 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.
- 762236 2y agoNow that gen AI can help write code, is a garbage collector necessary anymore?
- logicchains 2y agoManual memory management is one of the hardest things for LLMs to get right.
- hyperhello 2y agoWhat do LLMs “get right”?
- gadflyinyoureye 2y agoFlutter widget design.
- cratermoon 2y agoWhatever code examples got the most votes on StackOverflow.
- rustc 2y agoLeetCode questions. They often give the memorized LeetCode answer even if you slightly modify a question so it has a different correct answer.
- echelon 2y agoLovable, 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 2y agoI 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.
- liampulles 2y agoI 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 2y ago>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 2y agohttps://go.dev/doc/faq#generic_methods https://go.dev/doc/faq#generic_methods
- singron 2y agoThis 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_resolution https://en.cppreference.com/w/cpp/language/overload_resoluti...
- wseqyrku 2y agoI 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.
- int_19h 2y ago
- vessenes 2y agoYou 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.
- deleted 2y ago[deleted]
- maccard 2y ago> 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 2y agoI 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 2y ago> 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.
- cpufry 2y ago[dead]
- Groxx 2y ago>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 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 2y agoI’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 2y ago> 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.