Good, one of Go's strength is easy refactoring given that the standard library includes its own parser and syntax packages. If something gets renamed, just write a small script to replace all references...
But that doesn't scale at all unless you can push it back out. Aliasing and running this script both imply long term maintenance commitments, but the script is alot more invasive and requires a lot more work because its semantics are defined over the whole language as a transformer rather than as a single feature inside the compiler.
I think people suggesting a transformer script are inviting Go back into the dark pre-vendor days when every dependency got out of date rapidly and builds were "losable".
https://www.reddit.com/r/golang/comments/5alxa3/gos_alias_pr... points out that this isn’t actually a problem that is specific to Google, but rather affects the FOSS community as a whole, and I think the recent move of golang.org/x/net/context into the stdlib (as context/) is a perfect example.
The author of the article doesn’t seem to mention or consider this point at all.
Vendoring still requires that _all your dependencies_ (even vendored) use _either_ “golang.org/x/net/context” _or_ “context”, hence I don’t quite see how that addresses the problem. Can you elaborate?
Application I am working on was creating over 5k additional goroutines for context handling because part was using context from mainlib and part was using /x/net/context. The lib has special optimization that would fail if you mixed both.
To me the root problem for this alias proposal is that go currently ignores version dependencies.
Go is designed so that we have to assume that an API and the compiler are either perfectly backward compatible or immutable. This is a simplification inherited from C and other programming languages.
If we want to support changing an API, then we have to specify version along the package name and import statement. Some packages already do that but it isn't satisfying because it is hardcoded in the package name.
I like the versioning rule chosen by ICE from zeroc. A version is not backward compatible with other versions. A release must be backward compatible with previous release of the same version.
So it should be possible to specify a version and release for packages in import statements which specify that the specified release and all subsequent release are valid.
Dub, for the D programming language is going that line of direction.
I'm aware that it may create a dependency hell. But it already exist and is hidden. The alias hack is not a good solution. It will make things worse by making the rules more lax.
Forgive me for asking but are you saying that you want this kind of versioning baked into the language? That seems to add a lot of complexity to Go itself.
Java does not have versions baked into the language except for for class formats, which change rarely and are upwards compatible with new JDKs. Package versioning nowadays is handled by tools like maven and gradle. It's a good separation of concerns that keeps Java from being even more complicated than it already is.
If the concern is with handling interface versions, why not just use loosely coupled web services?
with big projects we will have dependencies with other packages. It is a good sign that this happens. But then we get also version dependency witch is currently ignored by go. I use gvm which is one way to handle it, but it is not enough because external package I use may have their own external dependency. The bigger the project, the bigger the problem will be.
Actually the version dependency is undpecified. One has to guess it by trying to compile. I don't think aliasing package names solves this.
You tell me that java tools take care of it. So go tools could take care of it. But this requires that the info is provided in a standard and clear way. This info can be kept optional for backward compatibility. Without this info, we are limited when trying to use go for bigger projects.
with big projects we will have dependencies with other packages. It is a good sign that this happens. But then we get also version dependency witch is currently ignored by go. I use gvm which is one way to handle it, but it is not enough because external package I use may have their own external dependency. The bigger the project, the bigger the problem will be.
Actually the version dependency is undpecified. One has to guess it by trying to compile. I don't think aliasing package names solves this.
You tell me that java tools take care of it. So go tools could take care of it. But this requires that the info is provided in a standard and clear way. This info can be kept optional for backward compatibility. Without this info, we are limited when trying to use go for bigger projects.
Yeah, but Go bundles dependency-fetching into the language, via import "github.com/somedude/somerepo". Doing so without supporting versioning makes versioning harder to accomplish via external tools than Java's "do nothing" approach.
Go is somehow based on the assumption that code is really carefully designed. The tools in /bin are the perfect example for that. Unfortunately commercial projects usually have a much faster development pace.
It definitely eases the problem, like proper dep mgmt would, but not more... ;)
I don't see what is wrong with including the version in the package name. `http`, `http2`, `http3` and so on. I mean if the API changes in a backwards-incompatible way it is effectively a new package.
I don't really get it. What's the big deal? I see a lot of dramatic language about the "strenuous objections of many external contributors", but what exactly is so bad about this proposal?
There was even a comparison to `goto`...? What am I missing?
I mean, I think Go generally holds that it cannot be nearly as complex as other languages with type aliases. It's a curious restriction for a language with pointers to take, I know... But I suppose that the way go implemented vendoring introduces some strange outcomes here.
Go pointers have no pointer arithmetic and are clearer than guessing what's being passed by value/reference and something like C#'s out/ref.
With or without pointers, as a programmer you'll have to understand passing by value and passing by reference. Go just makes it explicit in a language construct.
I do not think in/out parameters are ugly. I find them to be a lot more elegant than the cudgel that is pointers.
Go's pointers are certainly BETTER than C++ pointers, but I don't think that immediately makes it better. BOTH languages have value and reference semantics, and BOTH languages use syntax to dogwhistle that to programmers.
The main issue seems to be if A imports and re-exports symbol B from C, reading code that uses A.B you have no idea where to look to find out more about B; it may even be multiple hops of indirection to get to the original C.B, and that layer of cruft will never be removed, realistically.
...that said, I don't really see the problem either, except in that it will make go less 'simple' to read and understand.
I have worked on codebases where the deprecated annotation lasted years in the wild. Marking something as deprecated does almost nothing to make it possible to remove code. It doesn't even do all that much to prevent new uses most of the time.
Preventing it in the first place is a much more effective method.
Sure, my comment is only meaningful if someone bothers to fix the warnings. Before Vendan’s comment I didn’t know that Go doesn’t have warnings, so it’s moot anyway.
I was thinking in terms of upgrading a library to a newer version. It’s nice to at least get warnings for everything you’ll need to update, even if it doesn’t make it any easier to actually do so.
Yeah. Part of the problem that Go attempts to fix is exactly this. They just make it fail to compile if you don't update your code. In a large monolithic repository this is easier to police and manage. In the rest of the world not so much.
How would you possibly do that? Part of Go is that there are no warnings, only errors. If you want it to just error a build if you've "deprecated" a thing, just delete the thing...
If I've learned anything in nearly 20 years of professional software development, it's that nothing is temporary, least of all things that are explicitly labeled as such.
> except in that it will make go less 'simple' to read and understand.
Which probably will defeat the point of using Go... its main selling point for me it is its simplicity.
If they keep making it more difficult to work with, I might just end up going back to C++
Go scores infinity actually. Go generate is a part of the language, which means that you can bring your own Turing-complete language to the party.
Reflection especially with plugins, are essentially another language that is even more difficult to track down than macros (or templates). Though you can at least write Reflection using go.
but go generate isn't a part of the standard build process. If we go to that level, I can just say "most things that use C++ have a Makefile, so that's infinite too".
C preprocessor alone is not turing-complete; you have to repeatedly apply the preprocessor (until you get a fixpoint or decide the program won't halt) to perform arbitrary computation.
I get the feeling that C++ is becoming simpler to use with each edition. Perhaps some day if Go will be adding features such as this, they will meet half way through :)
It'd be a good idea, but it's REALLY important to remember that the path to Java (where your language is so full of makework tasks that it is only truly productive with a mountain of tooling) is a very short path with conversations like this.
Most language designers generally say that if your language requires a sidecar development environment to be reasonably productive, you've failed at your design tasks. Tools should be time saving helpers, not requirements to understand and navigate even basic code structures.
I think the big deal here is, a political one, more than a technical one. While google is the reason we have go and google basically pays for go, people are afraid that go might lose its simplicity in order to cater to google's wishes.
To me this alias proposal is pretty much the way it's been done in Java forever, so whenever you call for "foo.bar.Baz", the classloader is free to give you back a different implementation, including vendor/foo/bar/Baz, or even a mock implementation.
I think in any large scale dependency chain, you're eventually going to have two transitive dependencies D1 and D2 that both use dependency E, but different versions.
Inside Google we have the OneVersionPolicy, because we control everything in our repo, but in the external ecosystem, this is a frequent occurrence, that has to be solved by either classloader isolation, or upgrades.
- just because a company owns the domain doesn't mean they control the language. You need someone to own the domain, and it's always preferable if they are part of a company that won't disappear anytime soon. Similarly being able to unlist someone with powers isn't enough. If I pushed a commit replacing the Rust core team members with someone else it may go through, but nobody in the community will think that was legitimate and it would be reverted. Same with Go, surely.
- If Go has a defined governance model you can't say that Google controls it. If it's wishy-washy then you can't tell. IIRC it's wishy washy right now but ICBW, the last time I was doing go was a year ago. However, it's not clear that Google forced this type alias thing to happen.
-type aliases are a useful feature even without Google's use case. They might be a broken solution to Google's problem but they solve a lot of other things.
Dave Herman once gave a talk about "The Great Int Debate" in Rust. The core team had made a decision (pre 1.0) that many folks disagreed with, leading to resentment. The process was improved with a few rules both participants and decision makers had to abide by, and the re-discussion was more productive. The final decision was a different one, but everyone was happy with it. This evolved into the current set of processes for Rust's rfc process and is going strong.
I mean, yeah, not everyone, but it was no longer a very divisive issue; most of the community was okay with the outcome even if they disagreed with it. Much better than the previous round where there was a lot more resentment.
The one that controls the situation is the one that has most the power over it. Especially the one that can change the rules of the game. The change does not have to be sudden to be effective.
I am not familiar with Go community, so it is hard for me to say anything, but I think that this is something we should consider to get an objective view of the situation.
The problem is not the governance himself but how Go ties namespaces to API and the lack of flexibility in the language. It has nothing to do with who owns Go. The ownership issue is a distraction.
The post brought up two separate issues, one for how Go handles namespacing, and one about Google's control over Go. I'm addressing both. It may be a distraction, or it may not, but in case it's an actual problem the community is having, then trying to figure out a governance model is something they should do. If not, ignore.
The author mentions Go's vendor/ implementation, and I just have to comment about my issues with it (I am filled with the rage of a thousand suns each time I have to deal with vendor/). I hope this will serve as an example to other language designers to understand why you should always listen to your community. [ Disclaimer: I am a maintainer of runC and have been contributing to Docker and other Go projects for almost 3 years. ]
Go didn't have vendoring support in the old days, so the solution that people came up with was to hack with the GOPATH. The idea is quite ingenious, you just have to create a directory in your project of the form vendor/src/... and the set GOPATH=vendor:$GOPATH. Now, it's a hack around the fact that Go tries to take control of your filesystem (something that I'm still bitter about, and have near-daily issues with) but it's a fairly good one.
There are several benefits to this method that are quite crucial to point out:
1. It doesn't require any source code changes to implement. It's entirely a build system trick. It also makes the version tagging completely separate from the source code. Luckily this is true for the new vendor/ implementation. But note that this is actually a complaint people have about vendoring. At least this brokenness is maintained... Lovely.
2. You can turn off the vendoring by no longer changing your GOPATH. Or you could point your GOPATH to somewhere else.
3. Go's importing of the package path is unchanged by vendoring (look at the output of `go list -f '{{ .PkgPath }}'` to see what I mean). This may seem like a trivial thing to point out but it has an impact on building in certain contexts, as well as users of the reflect package.
4. It worked with every version of the Go compiler (even gcc-go).
There is also a few downsides (though you could see them as upsides). The main one is:
5. All of the vendoring is done globally. This means that if you pin a version of a repository, and one of your dependencies also uses that repository they will both use the same version. This could cause issues, but also ensures that you know what version of the code you're running.
But the solution that Go went with (where vendor/ becomes a magical directory within every project and changes how package imports are resolved) doesn't fix all of the same problems. Namely, it breaks points (2), (3), and (4). (2) is hard to do (you have to manually delete the vendor/ directories), (3) is just simply not possible to fix and (4) is a fun exercise to try to hack around (trust me, I've tried) but because (3) is broken it's not possible [ Did I mention that Go _really_ hates symlinks? ].
Now, you could argue that it fixes (5) (but the GOPATH trick could've fixed it too). So really the solution Go went with was worse than the solution the community came up with. And it was incompatible with the GOPATH trick (fixing (4) involves trying to convert the vendor/ to a GOPATH -- it only works sometimes but because the trick involves symlinks good luck with that).
And now Go is going to work on packaging. I can't wait to see how they break it. I really regret that the containerisation community went with Go as their language of choice.
Go essentially went with the node solution (node_modules). Go's pm will essentially just ensure the version that resides in vendor/, and will work more like yarn than npm. This is, of course, not unique to node.
Having a global vendor folder where everything is the same version regardless of the project seems like a fragile solution to me, especially if you work on a lot of projects. And how would that work with third party libraries? Those would still have to vendor?
I think it's a good thing that Go standardizes vendoring.
> Having a global vendor folder where everything is the same version regardless of the project seems like a fragile solution to me
That's not what I said. The way that every Go project used to do this (which was how Godep solved the problem) is that you have a vendor/src directory in the project. It's very similar to having vendor/ in your project, but the issues I mention are because of the lack of the "src" prefix. If it sounds like a trivial difference, that's because it is (but because of Go's lovely filesystem dependencies it makes everything awful).
> I think it's a good thing that Go standardizes vendoring.
Sure. I don't think it's a good thing that they standardised an incompatible solution, apparently ignoring how everyone else in the Go community had solved the problem before them.
This has an interesting interaction with another of Go's quirks.
As I read this (and it seems like the author has an axe to grind so maybe they aren't explaining the motivation correctly) the idea is that when you want to rename A to B, you can make an A -> B alias to ease the transition.
However, people calling your code and using A won't know that they need to update their code unless there's some kind of warning. But Go doesn't have compiler warnings.
So I think the author is right, this would be better implemented as a compiler error plus an automatic refactoring tool. That approach would leverage one of Go's strengths -- it's a pretty regular language and already has good refactoring tools.
Whether you like the alias proposal or not, the bubble problem is real. Go's maintainers live in a bubble and have blindspots around what Go is missing. They refuse to consider any of the usual language mechanisms for code reuse: subclassing, generics, templates, or macros. They repeatedly shoot down any discussion of these topics.
> One of go's defining features is the lack of these things. If you want them, use another language.
Go will end up having these missing features whether you like it or not. It might take a new generation of Gophers but it will happen. The alias problem is a nice illustration of how broken Go is.
Version conflicts and package naming collisions are two different problems. The OP referred to the version problems.
I overlooked package naming conflict which can occur with composed package names. It is true and correct that aliasing will help solving such type of problems.
Thanks to point this out.
PS: I suspect you have been down-voted because your critic is excessive (cf. "half backed feature").
73 comments
[ 4.2 ms ] story [ 139 ms ] threadOf note, this change has been reverted now[1].
[0] https://www.reddit.com/r/golang/comments/5alxa3/gos_alias_pr...
[1] https://github.com/golang/go/issues/16339#issuecomment-25852...
[0] https://github.com/golang/go/commit/87f4e36ce7d7dffbf1f2a869...
But of course it can still change.
I think people suggesting a transformer script are inviting Go back into the dark pre-vendor days when every dependency got out of date rapidly and builds were "losable".
The author of the article doesn’t seem to mention or consider this point at all.
The alias change could be useful in OSS, but it's certainly most useful in a monorepo.
Application I am working on was creating over 5k additional goroutines for context handling because part was using context from mainlib and part was using /x/net/context. The lib has special optimization that would fail if you mixed both.
Go is designed so that we have to assume that an API and the compiler are either perfectly backward compatible or immutable. This is a simplification inherited from C and other programming languages.
If we want to support changing an API, then we have to specify version along the package name and import statement. Some packages already do that but it isn't satisfying because it is hardcoded in the package name.
I like the versioning rule chosen by ICE from zeroc. A version is not backward compatible with other versions. A release must be backward compatible with previous release of the same version.
So it should be possible to specify a version and release for packages in import statements which specify that the specified release and all subsequent release are valid.
Dub, for the D programming language is going that line of direction.
I'm aware that it may create a dependency hell. But it already exist and is hidden. The alias hack is not a good solution. It will make things worse by making the rules more lax.
What's preventing a tool like pip, npm, or stack from working with Go for versioning?
Java does not have versions baked into the language except for for class formats, which change rarely and are upwards compatible with new JDKs. Package versioning nowadays is handled by tools like maven and gradle. It's a good separation of concerns that keeps Java from being even more complicated than it already is.
If the concern is with handling interface versions, why not just use loosely coupled web services?
Actually the version dependency is undpecified. One has to guess it by trying to compile. I don't think aliasing package names solves this.
You tell me that java tools take care of it. So go tools could take care of it. But this requires that the info is provided in a standard and clear way. This info can be kept optional for backward compatibility. Without this info, we are limited when trying to use go for bigger projects.
Actually the version dependency is undpecified. One has to guess it by trying to compile. I don't think aliasing package names solves this.
You tell me that java tools take care of it. So go tools could take care of it. But this requires that the info is provided in a standard and clear way. This info can be kept optional for backward compatibility. Without this info, we are limited when trying to use go for bigger projects.
Having the version in the package name is useful, as an incompatible API change usually requires visiting the code and this forces the issue.
It definitely eases the problem, like proper dep mgmt would, but not more... ;)
There was even a comparison to `goto`...? What am I missing?
With or without pointers, as a programmer you'll have to understand passing by value and passing by reference. Go just makes it explicit in a language construct.
Your second paragraph seems to invalidate your first?
Go pointers make references and values quite visible to the programmer, because in the real world it's not actually convenient to hide those details.
Go's pointers are certainly BETTER than C++ pointers, but I don't think that immediately makes it better. BOTH languages have value and reference semantics, and BOTH languages use syntax to dogwhistle that to programmers.
...that said, I don't really see the problem either, except in that it will make go less 'simple' to read and understand.
It will if you mark the alias as deprecated. Unfortunately, there seems to be no standard way to do that in Go.
Preventing it in the first place is a much more effective method.
I was thinking in terms of upgrading a library to a newer version. It’s nice to at least get warnings for everything you’ll need to update, even if it doesn’t make it any easier to actually do so.
Which probably will defeat the point of using Go... its main selling point for me it is its simplicity. If they keep making it more difficult to work with, I might just end up going back to C++
I honestly think you're just being dramatic here, there's no way Go catches up to C++ in that respect any time soon.
On this metric, Go scores 1 (just Go itself), and C++ scores 3 (C preprocessor, C++ itself, C++ templates).
Reflection especially with plugins, are essentially another language that is even more difficult to track down than macros (or templates). Though you can at least write Reflection using go.
I don't think the language as a whole is getting any simpler.
Most language designers generally say that if your language requires a sidecar development environment to be reasonably productive, you've failed at your design tasks. Tools should be time saving helpers, not requirements to understand and navigate even basic code structures.
I think in any large scale dependency chain, you're eventually going to have two transitive dependencies D1 and D2 that both use dependency E, but different versions.
Inside Google we have the OneVersionPolicy, because we control everything in our repo, but in the external ecosystem, this is a frequent occurrence, that has to be solved by either classloader isolation, or upgrades.
And about fashion: yes, fashion is better when innovates. Without innovation fashion just can't exist.
- just because a company owns the domain doesn't mean they control the language. You need someone to own the domain, and it's always preferable if they are part of a company that won't disappear anytime soon. Similarly being able to unlist someone with powers isn't enough. If I pushed a commit replacing the Rust core team members with someone else it may go through, but nobody in the community will think that was legitimate and it would be reverted. Same with Go, surely.
- If Go has a defined governance model you can't say that Google controls it. If it's wishy-washy then you can't tell. IIRC it's wishy washy right now but ICBW, the last time I was doing go was a year ago. However, it's not clear that Google forced this type alias thing to happen.
-type aliases are a useful feature even without Google's use case. They might be a broken solution to Google's problem but they solve a lot of other things.
Dave Herman once gave a talk about "The Great Int Debate" in Rust. The core team had made a decision (pre 1.0) that many folks disagreed with, leading to resentment. The process was improved with a few rules both participants and decision makers had to abide by, and the re-discussion was more productive. The final decision was a different one, but everyone was happy with it. This evolved into the current set of processes for Rust's rfc process and is going strong.
https://air.mozilla.org/friday-plenary-rust-and-the-communit...
Go might want to look into governance models like these if they truly are a problem. From one blog post, I can't really tell.
For what it's worth, that worst-of-all-worlds solution was what made me give up on Rust back on 0.9, and I haven't really missed it since.
Sorry you had to leave.
I am not familiar with Go community, so it is hard for me to say anything, but I think that this is something we should consider to get an objective view of the situation.
The problem is not the governance himself but how Go ties namespaces to API and the lack of flexibility in the language. It has nothing to do with who owns Go. The ownership issue is a distraction.
Go didn't have vendoring support in the old days, so the solution that people came up with was to hack with the GOPATH. The idea is quite ingenious, you just have to create a directory in your project of the form vendor/src/... and the set GOPATH=vendor:$GOPATH. Now, it's a hack around the fact that Go tries to take control of your filesystem (something that I'm still bitter about, and have near-daily issues with) but it's a fairly good one.
There are several benefits to this method that are quite crucial to point out:
1. It doesn't require any source code changes to implement. It's entirely a build system trick. It also makes the version tagging completely separate from the source code. Luckily this is true for the new vendor/ implementation. But note that this is actually a complaint people have about vendoring. At least this brokenness is maintained... Lovely.
2. You can turn off the vendoring by no longer changing your GOPATH. Or you could point your GOPATH to somewhere else.
3. Go's importing of the package path is unchanged by vendoring (look at the output of `go list -f '{{ .PkgPath }}'` to see what I mean). This may seem like a trivial thing to point out but it has an impact on building in certain contexts, as well as users of the reflect package.
4. It worked with every version of the Go compiler (even gcc-go).
There is also a few downsides (though you could see them as upsides). The main one is:
5. All of the vendoring is done globally. This means that if you pin a version of a repository, and one of your dependencies also uses that repository they will both use the same version. This could cause issues, but also ensures that you know what version of the code you're running.
But the solution that Go went with (where vendor/ becomes a magical directory within every project and changes how package imports are resolved) doesn't fix all of the same problems. Namely, it breaks points (2), (3), and (4). (2) is hard to do (you have to manually delete the vendor/ directories), (3) is just simply not possible to fix and (4) is a fun exercise to try to hack around (trust me, I've tried) but because (3) is broken it's not possible [ Did I mention that Go _really_ hates symlinks? ].
Now, you could argue that it fixes (5) (but the GOPATH trick could've fixed it too). So really the solution Go went with was worse than the solution the community came up with. And it was incompatible with the GOPATH trick (fixing (4) involves trying to convert the vendor/ to a GOPATH -- it only works sometimes but because the trick involves symlinks good luck with that).
And now Go is going to work on packaging. I can't wait to see how they break it. I really regret that the containerisation community went with Go as their language of choice.
Having a global vendor folder where everything is the same version regardless of the project seems like a fragile solution to me, especially if you work on a lot of projects. And how would that work with third party libraries? Those would still have to vendor?
I think it's a good thing that Go standardizes vendoring.
That's not what I said. The way that every Go project used to do this (which was how Godep solved the problem) is that you have a vendor/src directory in the project. It's very similar to having vendor/ in your project, but the issues I mention are because of the lack of the "src" prefix. If it sounds like a trivial difference, that's because it is (but because of Go's lovely filesystem dependencies it makes everything awful).
> I think it's a good thing that Go standardizes vendoring.
Sure. I don't think it's a good thing that they standardised an incompatible solution, apparently ignoring how everyone else in the Go community had solved the problem before them.
As I read this (and it seems like the author has an axe to grind so maybe they aren't explaining the motivation correctly) the idea is that when you want to rename A to B, you can make an A -> B alias to ease the transition.
However, people calling your code and using A won't know that they need to update their code unless there's some kind of warning. But Go doesn't have compiler warnings.
So I think the author is right, this would be better implemented as a compiler error plus an automatic refactoring tool. That approach would leverage one of Go's strengths -- it's a pretty regular language and already has good refactoring tools.
Go will end up having these missing features whether you like it or not. It might take a new generation of Gophers but it will happen. The alias problem is a nice illustration of how broken Go is.
I overlooked package naming conflict which can occur with composed package names. It is true and correct that aliasing will help solving such type of problems.
Thanks to point this out.
PS: I suspect you have been down-voted because your critic is excessive (cf. "half backed feature").
You cannot separate both issues. Go assumes all API are immutable which is insane, a package manager won't solve that.
Even with the subsequent explanation, this is a tiresome level of what I can only call paranoia.