Recently I was discussing Go with a few colleagues who are new to the language and they mentioned several things that they wished Go supported or handled differently. The specifics aren't important, but it got me thinking. What do you think Go could/should do better?
Personally, I'd like to see more informative compiler errors. Consider the following, from Rust:
error: 'id' is declared as an argument...
--> src/main.rs:8:9
|
8 | #[get("/<id>")]
| ^^^^
error: ...but isn't in the function signature.
--> src/main.rs:9:1
|
9 | fn hello() -> &'static str {
| _^ starting here...
10 | | "Hello, world!"
11 | | }
| |_^ ...ending here
This looks amazing with line numbers, indentation, underlined arguments and block markers. I'd love to see something similar in Go.
评论:
throwlikepollock:
weberc2:I don't know how it could be done, or if it's even possible, but i'd love to see better safety from nil pointes/interfaces in the spirit of Rust.
Not saying the borrow checker specifically (though i do love it), but if we could somehow have more safety from pointers in Go it would make me very happy.
Coming back to Go is a chore after you get used to how correct and safe your Rust code is.
andradei:Algebraic data types would be stellar.
anacrolix:Yes!! But how do you match extensively with the current spec? Would it be enforced by the compiler? I'd hope so.
FUZxxl:Yes.
jmoiron:They should do away with the half-assed
syscall
package, remove the various design oversights and throw out those functions they replaced with better versions. Of course, all of this should happen in Go 2 as to not break compatibility.It would also be nice if the semantics of functions were explained in a more detailed manner in the standard library. Right now it's not possible to accurately build a Go implementation just based on the documentation, which is an absolute requirement for a system that wants to be more than a single implementation.
Velovix:syscall is officially deprecated in favour of x/sys
mixedCase_:For what it's worth, Go already has a second implementation, gccgo.
DualRearWheels:And gopherjs, and llgo. While the first is incomplete by design and the latter seems outdated, it still means more than two teams are testing the spec for its limitations.
kiddcode:Mode for no overhead C calls, even at expense of goroutines or GC (eg. simulate C stack, goroutine is actual OS thread or something like that, I don't know much about this). Not all applications require large number of threads (nor frequent creation), and this would make Go perfect to use with C libs.
Edit: or somehow to start goroutine that is actually OS thread with C stack and no C call overhead, while other goroutines in program are unchanged (behave same as now). This would enable best of both worlds.
whizack:I'd like to see non-nillable pointer types. It feels weird and arbitrary that since I'm pointing to something that I have to check for nil all over the place.
commentzorro:or you could use the https://en.wikipedia.org/wiki/Null_Object_pattern
jerf:I agree with FUZxxl. This does look to be really awkward compared to non-nillable types.
FUZxxl:Go already has that, really. Dereferencing a nil is not instantly an error, because pointers in Go aren't just pointers, they're semantically a (type, pointer) pair, so you can still get to the method through the type even if the pointer is nil.
Which is sometimes useful; I have several types for which "nil" is perfectly valid and meaningful. It's exceptional enough of a case that I always document said legality in the godoc and carefully explain what it does, because it seems a non-trivial number of people think nil is always invalid, and the remaining people who know it's valid probably (generally correctly) assume that a pointer can't be nil if you don't say it can.
However, it can also mean that when you have a nil that shouldn't be nil, the error message can end up even farther away from the real problem, because you can end up passing through even more method calls before something finally crashes out rather than crashing earlier on an illegal dereference.
I'd still like to make non-nillable types.
dgryski:Great. Get rid of null pointers so you can have shitty use-case specific null objects that don't even crash your program when you access them. DEC called, they want their null pointer semantics back.
thockin:I wonder how much you could do outside the compiler with static checks and annotations? Interesting way to prototype.
dgryski:Const pointers that can't be unconsted.
Auto-generated deep-equal and deep-copy, without reflect.
Allocate-and-init for primitives ('new string("foo")"
Non-copyable types (mutex).
Auto-allocating maps, without 'make'.
Off the top of my head...
thesnowmancometh:Pointers and circular data structures complicate deep equal and deep copy. You need to define semantics per type here.
dgryski:For maps, can't you just do...
map[string]int { "Hello": 5 }
You can allocate the map without needing
make
. Looking at the implementation at https://golang.org/src/runtime/hashmap.go , you don't immediately allocate all of the buckets you might need, but then again if you're really trying to tune the allocation of the number of buckets in the default map type, you probably want something more specific than the default map type. I can see where you're coming from though, if I'm understanding you correctly :)
egonelbre:Const is tricky. You might be interested in Russ's evaluation of the proposal for read-only slices https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A/mobilebasic
Sythe2o0:Non-copyable types (mutex).
Not the same, but... go vet (-copylocks) warns about copying types that have a
Lock
method. 1
itsmontoya:Being able to work with maps / slices of interfaces as interfaces themselves, or with interface keys or values: example
tqwe2134:This was a huge pain in my ass when writing my Mustache parser.
arcagenis:Iterables. I'd be happy even if it was implemented by convention.
currently convention is
for n.Next() { current := n.Value() }
if this is the settled convention, it would be nice to support this with the range keyword
for _, current := range n { // do }
gallifreydweller:You can return a chan and then iterate over
func (n *N) Iterate() <-chan SubN { // ... } for elem := range n.Iterate() { // do }
jcbwlkr:SQL package really needs a way to bind a row to an structures rather than having to do each field manually.
chmikes:Yeah that would be great to see in the std lib. You know about https://github.com/jmoiron/sqlx right?
goomba_gibbon:Support package version. Vendor is a hack.
knotdjb:This is in progress, I believe. I heard we would get something very soon on the gotime podcast.
sh41:I'm on the bandwagon against naked returns.
Also defining a function with only the types as the parameters (no names), e.g.
func foo(int) { return }
I can see how unspecified names for parameters are fine if unused, but I'd much prefer stricter syntax since they're confusing.
neoasterisk:I think it'd be really nice if it could/should support WebAssembly as a target. :P
vorg:Underrated.
dgryski:It would be nice if we see a clear separation between the features of the programming language as specified by the Language Spec, and the features and toolchain as implemented by the distro at golang.org.
I'd concede it's highly unlikely the reason we haven't heard about another implementation of Go is someone's out there right now working in secret on an implementation that conforms to the published spec, but a clearly written spec that's distributed distinctly from the Google-backed golang.org implementation would mean it's possible that others could make a better implementation of Go if they want to.
So I think your example of the informative compiler errors would be nice in the golang.org implementation, but not in the Go language spec.
ActualDonaldJTrump:Gccgo and gopherjs both implement the specs are able to use much of the standard library as is.
Gccgo in particular was helpful in finding places where the spec and the gc compiler disagreed.
karma_vacuum123:A proper extensible attribute syntax. Struct tags are OK, I guess, but I hate how certain comments like
//go:generate
,//export
, and// #cgo
are treated as compiler directives and all use a different syntax.Also, I think
export
should just be a language keyword, now that the shared library build type is supported. That way I can export functions like this:export func DoSomething() { }
groob_mobile:your function export example adds nothing over the current mechanism...what would be cool though is something like Perl6's export tags...which allow you to provide a named tag along with the visibility attribute...which could be thoughtfully applied to mitigate some of the scenarios people vendor against (namely, you could tag functions as
stable
,beta
etc)
callcifer:The discussion about the rust/elm-like errors came up as a proposal.
https://github.com/golang/go/issues/17495
It's a pretty good thread. I think I most agree with the conclusion at the end. To an experienced user, the Go error are mostly easy to read/follow and this new stile would get in the way.
I did experiment with /u/bradfitz suggestion by modifying the
gb
tool to parse errors as well. It's definitely doable should someone take on a project like that.
d_rudy:That is indeed a good thread. Reading through it, I would personally be happy with #10324 being implemented, so tooling alone could solve this problem.
DeedleFake:Only thing I'd really like is lambda expression syntactical sugar. If I want to do some functional-style iterations, it's kind of awkward currently.
myMap.Filter(func (v MyType, k string) MyMapType { /* do something */ })
as opposed to:
myMap.Filter(|v, k| => /* do something */)
I would figure the type signature of
filter
would let you know whatv
andk
are and what the lambda should return, so it's not breaking type-safety. I just think it would make Go a little more fun to write. Thefor
loops get a little tedious after a while, but that might just be my preference.EDIT: Eh, the more I think about it, I'm not sure Go is cut from the right cloth to do stuff like this. I'm hard-pressed to think of how to truly make this useful without generics. Oh well,
for
loops it is.
rony358:Maybe optionally. Personally, I find Rust's errors incredibly obtuse, especially when they point to the wrong part of the code, which they did pretty often back when I tried it.
whizack:This should have gone away in the last few months. Which version did you see those in?
dgryski:personally, i'd like to see more debug information provided in the base error type by default (like stacktrace, line numbers, files, etc.) rather than just a special stringer interface
karma_vacuum123:Something like https://github.com/pkg/errors might end up in the standard library.
szabba:wow neat and i wish the original std package had those features
commentzorro:Do you think there's place in the library for smth like https://play.golang.org/p/76ivuvbSpt ?
lobster_johnson:I'd like to see something like a way to pass types as parameters and, ideally, use these types against functions etc. at compile time and such. I don't know what to call it. Some common general inclusive term probably?
moose_cahoots:First-class types. I agree,
reflect.Type
is a pretty awkward interface.
youguess:I would love to see an easy way to replace all the 'if err != nil { return err }' blocks sprinkled everywhere. It is so common that I long for the ability to create macros like in C++.
goomba_gibbon:Would you mind extending your comment?
I have no clue what you are referring to, why would you want to replace the error checks?
And why don't you just use an editor that supports (and understands) go? That way you can easily refactor.
And if all else fails there are snippets. The whole thing is for me errr<tab> (error return) or errh<tab> (error handle)
youguess:It's still verbose, even if you use your editor to make it easier.
I'm personally ok with the syntax now but I can understand why it irks people.
sin2pifx:Well, it is verbose yes, but then again similar constructs look like
try: conquer_the_earth() except AllHellBrakesLoose as e: recover(e)
not really less verbose, just different. The only thing is that you can let it bubble up without doing anything.
But I don't think a caller 2 or 3 levels up cares about an index error or knows why that happened
goomba_gibbon:While some of the language features mentioned here would be nice, a working debugger would be a more practical start: dlv just doesn't work on OSX 10.12, which was released 3 months ago.
sin2pifx:It's nice that this problem was solved by the community but I think a debugger, like package management, is something the go team should have taken in-house.
Perhaps that's unfair, and I know it's not simple, but it would have been nice if there had been a debugger for everyone from the start.
bannerad:I had no idea it was a community built tool. I'm surprised. It seems so logical to provide debugger support when you develop a programming language, especially when the language is 7 years old.
DavidDavidsonsGhost:Dependency management.
elingeniero:I might be missing something but when I recover I want a stack trace by default. If I can't control what panics then I have no way to ensure a stack trace is included in the panic func.
DavidDavidsonsGhost:Dependency management.
Get rid of the fucking GOPATH nonsense and have a good way of handling per project dependencies that isn't completely insane.
Also, management of dependencies.
neoasterisk:I am with you on this. I find it very annoying that stuff breaks if you try to do it outside of a workspace.
dgryski:Personally, I'd like to see more informative compiler errors.
Go's compiler errors are one of the best I've seen in my life. If you have any doubt about that, try working with C, C++ and Java. Even today a missing semicolon error can make me scratch my head for a few seconds.
Rust's compiler errors might be better than Go's but it is probably because it is such a complex language that without detailed compiler errors you wouldn't survive writing it.
That said, if Go can improve the compiler errors even more I obviously wouldn't mind. Which makes me wonder, is it against the Go compatibility promise?
neoasterisk:The compiler errors are getting better in 1.8 and 1.9. That was one of the reasons for moving away from the yacc-based parser to a hand-rolled one.
goomba_gibbon:The compiler errors are getting better in 1.8 and 1.9. That was one of the reasons for moving away from the yacc-based parser to a hand-rolled one.
Dope!
commentzorro:Awesome
3264128256:I'd like to see a fast (non channel based) C# style yield return construct.
anacrolix:Why even comment? It has zero chance of happening, more so now that they don't even check this subreddit.
karma_vacuum123:Yeah, this is a hive of scum and villainy and clearly not useful.
neoasterisk:
more thought put into interfaces in the standard lib, not just concrete APIs. looking at most of the standard lib, you would not know interfaces were important in Go
make
go tool vet
andgolint
unnecessary. if people are really going to be dogmatic about their ideas of idiomatic Go, just make them compile errors so we can avoid all the superfluous PRs and "bug" reportssomething like
Maybe
as a major addition to the standard lib. we have some generic constructs...why not one more? it's worth has been proven elsewhere when implemented appropriately
karma_vacuum123:make go tool vet and golint unnecessary. if people are really going to be dogmatic about their ideas of idiomatic Go, just make them compile errors so we can avoid all the superfluous PRs and "bug" reports
If you do not like your code getting better by the community then simply mention in your README that you do not accept PRs or at least that you do not care about writing idiomatic Go code (nothing wrong with that).
Do not take away tooling that helps everyone else.
neoasterisk:you misread. i am not opposed to these tools, i am saying that if we all agree that that the changes suggested by these tools always constitutes valid advice, then just turn their advice into compiler errors so we can stop having silly discussions about what is/isn't idiomatic code. you code will either be valid by these tools or it won't compile. yeah, there are some corner cases, but most of
golint
is actually stuff that could be easily enforced by the compiler
indil7:I don't disagree but this is tricky. There are already many complains about unused imports. Imagine if the compiler started complaining about missing comments or something like that.
On the other hand, things that actually matter like shadowing would be very useful. Unfortunately those cases always have false positives so I don't think it can be done reliably.
- full generics
- algebraic data types
- declared interface impls
- implement interfaces for types in other packages
- interfaces and methods for primitive types
- custom operators
- full type inference
- simplified var declarations
- debugger
- range/index/etc work with user types
- exceptions
- equality for slices, maps, and functions
- cleaner godoc presentation that doesn't just look like pure code
- use upper- vs. lower-case for types/variables instead of exported/unexported
- repl
- type aliases and re-exporting
- dependent types for arrays and custom types like vectors
- useful zero map values for key/value insertion and zero slice values for indexing
- remove special pass-by-reference semantics for slices and maps
- use something other than * for pointers to avoid conflict with multiplication
- naked returns are useless
- disambiguate recover() results (panic(nil) and panic(myaccidentalnilvar) look like no panic happened to recover())
- go tool support for managing deps
so many more too
jmoiron:EvilGeniusAtSmall:You basically want a different language.
indil7:It seems like a flippant remark but it's the correct response. Go is highly opinionated by design, and argues well for its design decisions. If you take that away and start changing the language fundamentally, maybe you produce a better language, but it's no longer Go.
I strongly invite anyone to put their money where their mouth is and fork the language and implement these features. If they are in fact better, I'd switch languages and I think others would too. But if you just collect up a grab bag of things that you would do different in a different language, that's not actually how you make Go better, that's how you make a language better than Go.
EvilGeniusAtSmall:Yes, it was flippant, and no, it's not a "correct" response to shut someone down when they're genuinely trying to participate in the discussion, and no, it's not "correct" in the sense you meant either.
Everything (or almost everything; I don't have the list in front of me on my phone, but I think everything IIRC) I listed is already part of Go; it could just be done better. I didn't list a homoiconic syntax or a concatenation semantics or a dynamic type system or anything un-Go-like. Go already has generics and algebraic types in part, for example. These aren't fundamental changes that would make Go a different language; they would make it a better language by improving and expanding on aspects and features that are already present. Your suggestion that the only recourse is to fork Go is absurd and unproductive, and dampens the discussion OP was looking for.
Actually, Go doesn't argue well for some of its design decisions. For example, the Go Team's reason for not having full generics is that they don't know how to do it well. That's not "highly opinionated", that's "punt it down the road until later". The reason the Go Team didn't add algebraic types was because they had already added type assertions—which enable a similar functionality without the type safety, clarity, simplicity, and generality—and the language was quite far along, and at that point they didn't want to make such a large change to the language (source: Ian Lance Taylor, a core Go contributor, replying to me on Reddit). In other words: "oops, too late!" Again, not "highly opinionated" as in some grand vision perfectly executed.
itsmontoya:full generics
Why? What's wrong with interfaces as they are today?
EvilGeniusAtSmall:Interfaces are fantastic, but don't completely eliminate the need for generics. Right now, I have to do code generation in order to get proper type fns for my helper libs.
itsmontoya:helper libs
Ah! Ive made this mistake myself. Because the language won't let you do what you want to do yet cleanly//elegantly/without repeating yourself, you made a helper lib, and used generics to code gen your way around the problem.
But what you really wanted was the language to support what you were trying to do in the first place in a more clean way. I bet you I could come up with a better way to represent your solution by enhancing the language and NOT adding support for generics, which is exactly the Go way.
Adding generics introduces a largely intractable problem without resorting to very messy code gen, and there are cleaner ways of expressing code reuse that are idiomatic Go, just not yet supported in the language. Push for those clean, elegant, idiomatic language solutions, not messy ones that cause more problems than they are worth.
EvilGeniusAtSmall:I disagree, because the stdlib uses generics to handle the same issues I've experienced.
itsmontoya:You don't think the language can be enhanced to eliminate the problem? Have you seen how much Go has changed since it was first released? That's literally been the path it's been on since the start. Why would you expect that to suddenly change now?
indil7:Tell me how you would enhance the language (without generics) to properly handle something like a channel.
Go ahead, I'll wait
callcifer:How can it be idiomatic if it's not in the language?
If you're already going to enhance the language, why not add generics? What better solves the problem?
How is boxing generic values messy or intractable? It seems straightforward to me, and it happens anyway when you use empty interfaces.
TUSF:Interfaces in Go only cover a tiny bit of what generics can provide. For example, how would you implement the following pseudocode function with only interfaces, without using reflection:
func MergeSlices(x, y []<T>) []<T> { ret := []<T>{} for _, val := range x { ret = append(ret, val) } for _, val := range y { ret = append(ret, val) } return ret }
callcifer:MergeSlices is a bad example, seeing as you can just do that with:
append(x, y...)
EvilGeniusAtSmall:True, MergeSlicesWithoutDuplicates would be a better example.
callcifer:And you have your answer: In terms of a interface that exposed my equality comparator.
EvilGeniusAtSmall:my equality comparator
What is your equality comparator?
thesnowmancometh:An object that provides equality semantics so I can deduplicate, potentially implemented in terms of reference comparison, but not necessarily. This way I can provide custom equality semantics.
This is idiomatic Go. If you look at sort, it works the same way. This is how the language was designed.
That's my point: don't bend the language to your thinking, understand it's thinking. Go is all about doing things a certain way. If you want to do things a different way thats not idiomatic Go, don't use Go. Use a language that code would be idiomatic in instead. You'll feel less pain.
indil7:Is this a serious comment?
neoasterisk:Be aware that generics and interfaces aren't the same thing: generics are parameterized types; interfaces are assertions that methods (in Go's case) exist for a type.
There are useful things that can't be expressed without generics. For example, Go arithmetic and comparison operators require the operands are the exact same type. That's useful. How can you do that for your own function? You can't, because that's generics.
How about a function that can operate on any slice? Append can do it, but it doesn't have a function type expressible in the language, because that's generics. For that reason, append arguably isn't even a function; it's merely a primitive baked into the language like arithmetic or channel operations.
Take a look at the builtin package in the standard library; practically everything in there is an example of things in the Go language that can't be expressed by its type system because it's too limited to describe them.
I'll talk about algebraic types as well since I'm talking about things in Go that its type system can't describe. How do you make a number type from scratch? That is, a new, unique type with multiple, unique values that aren't defined in terms of, or built on top of, some other type? The answer is you can't. There is no way to express the "int" type in the Go type system, because it's an algebraic type. So it's baked into the language itself. We're given int out of the box because there's no way to express it ourselves.
Basically, Go's type system is incapable of describing some of its own basic concepts and constructs without generics and algebraic types.
Picture the values in a language as a hand. The type system is the glove. Without generics and algebraic types, it's a bulky, thick, course, hot knitted mitten that keeps the four fingers stuck together. It's awkward to use the fingers, and everything feels distant and muffled through the glove. All you can really do is pinch things with your thumb and mash things with your fist. It's not a good fit, and so it hampers what the hand can do.
With generics and algebraic types, it's a light, slim, comfortable, dexterous leather glove that snugly fits every finger and feels light as a feather; you don't even really notice it's there, and the hand can do anything it normally can do.
Some people like dynamically types languages because they feel less burdensome than statically typed languages. They like that you can just write code and not fight the type system. They don't realize that static type systems aren't inherently bad; they were just using shitty ones. Static type systems can express anything that can be done in dynamic type systems. In many cases, the types can be inferred without explicit type annotations, so you get the best of both worlds.
There is zero SEMANTIC reason for a language to not have full generics and algebraic types. There CAN be valid implementation reasons, which is what the Go Team is partly stuck on now. Personally, I don't get what the performance hang up is about just boxing generic values; they get boxed anyway when I stick them in an empty interface; but I'm not up to speed on all that, so I dunno, but I've never seen that point addressed.
Hope that made sense!
indil7:
- full generics
- algebraic data types
- declared interface impls
- implement interfaces for types in other packages
- interfaces and methods for primitive types
- custom operators
- full type inference
- simplified var declarations
- debugger
- range/index/etc work with user types
- exceptions
- equality for slices, maps, and functions
- cleaner godoc presentation that doesn't just look like pure code
- use upper- vs. lower-case for types/variables instead of exported/unexported
- repl
- type aliases and re-exporting
- dependent types for arrays and custom types like vectors
- useful zero map values for key/value insertion and zero slice values for indexing
- remove special pass-by-reference semantics for slices and maps
- use something other than * for pointers to avoid conflict with multiplication
- naked returns are useless
- disambiguate recover() results (panic(nil) and panic(myaccidentalnilvar) look like no panic happened to recover())
- go tool support for managing deps
so many more too
Go is not the language you are looking for.
Nathanfenner:Why do you think I'm looking for a language like Go? I've clearly already found it and learned it...
Then you say: Well, no, I actually meant that Go won't ever change to do that stuff.
Then I say: Isn't the premise of the question that Go can change?
- equality for slices, maps, and functions
Would slices and maps be compared by value, or reference (I.e., would two slices with the same contents compare equal, or do they actually have to point to the same underlying array in the same place)?
As for functions, I'm actually glad that Go doesn't allow them to be compared. The best you can do is reference equality; but with closures it becomes unclear whether two functions are equal:
func f(n int) func() int {
return func() { return n }
}
// Surely, f(4) != f(5), but how do we know?
indil7: tscs37:By value. Compares lengths and elements for matching indexes.
Functions obviously can't be compared for mathematic equality, but identity equality is still useful. For example, let's say you use Rob Pike's functional options idiom for a method. How do you mock that method, since the mock can't tell whether the function you pass in is the right one?
I think since functions are a special kind of value anyway, it would be fine to have function equality be simple identity equality. Nothing else is useful and also possible.
RIC_FLAIR-WOOO:Macros. Lots of macros. Like in LISP.
comrade-jim:I, too, read Hacker News.
It's an awful site filled with corporate shills.
