Go 1.11 got me to stop ignoring Go
drewdevault.comA few years into using Go, I have mixed feelings about `GOPATH`. On one hand, I can see the author's frustration in that it was always incredibly presumptuous of the language's authors to dictate how its users should organize their hard drives, and more so it feels like exactly the type of arrogance that people tend to attribute to the Go's core and community.
Also, having helped a number of people now through their early days of the language it's also the one biggest thing by far that reliably confuses every single person. And I mean everyone — from first time programmers all the way up to people who've been in the industry for decades and are learning Go as their tenth language. Being forced to put files in certain places is incredibly non-intuitive because there's nothing else out there that requires it. I must have sent the Go documentation on workspaces [1] to two dozen different people at this point.
But on the other hand, once you've grasped the system and are using it, `GOPATH` is surprisingly not bad. It's always obvious where your dependencies are located and which versions are going to be used to build your project. Even better, it lets you very easily drop into those dependencies and add minor changes or debugging lines if you need to. This can be incredibly useful if you're trying to understand how one of them works or think that that you might have found a bug and trying to verify or patch it. A very powerful feature once you know about it.
The new Go modules seem good, and will be a huge improvement in lowering the barrier to entry for Go, but I'll miss the old `GOPATH` style of work at this point.
There's going to be a large group of people (myself included) who graps the system but refuses to use it because it's pointless and stupid.
> It's always obvious where your dependencies are located and which versions are going to be used to build your project.
More obvious than specifying the version in some dependencies file?
> Even better, it lets use very easily drop into those dependencies and add minor changes or debugging lines if you need to.
What dependency management system doesn't allow you to do this if you insist?
> More obvious than specifying the version in some dependencies file?
I have nothing against dependency files, but yes, it's probably even more obvious.
If I see an `import "github.com/x/y"` at the top of a Go file, I know that I can go find the source for that at `cd $GOPATH/src/github.com/x/y` (unless the project has a `vendor` directory in which case you'll look in there instead).
> What dependency management system doesn't allow you to do this if you insist?
I'm sure you could do it on anything, but Go makes it very easy.
As noted upthread, it's pretty simple to do this in other languages like Ruby when you're using Bundler and know the system well (e.g., `pushd $(bundle show excon)`), but in many languages it's much more difficult. You'd need to go chase down the source, download it, then redirect your dependency to point to it. Sometimes you'll grabbed a newer version that's no longer compatible so you'll have to make sure to work off of the right tag.
In bad cases (e.g., C, Java) you'd have to learn how to build the project which might not be trivial because tooling isn't standardized or convenient to use.
> What dependency management system doesn't allow you to do this if you insist?
Dependency management systems that require you to hunt for where they stashed whatever they sucked down based on the versions you listed in some file might, in some cases, make this particular task more complex than it strictly needs to be.
> Even better, it lets use very easily drop into those dependencies and add minor changes or debugging lines if you need to.
I've done this quite a bit with typical Ruby+bundler setups. I think the key is that both Go and Ruby start from source code, vs. something like Java where your dependencies are compiled JARs without source (of course, there is infrastructure to enable easy fetching of the source when available).
This is similar to my experience with Nix package manager. It's purely source-based with binary packages seen as something like a compiler cache. It's easy to get the source to a package, make some tweaks, and build (and use) a custom version (at least I personally have found it far easier than Debian, even with apt making it easy to get the source).
> Even better, it lets you very easily drop into those dependencies and add minor changes or debugging lines if you need to. This can be incredibly useful if you're trying to understand how one of them works or think that that you might have found a bug and trying to verify or patch it.
You should totally check out Roger Peppe's gohack[1] It enables you to do the same in a Go modules world
As someone who hasn't used Go, it sounds like GOPATH is very similar to an Eclipse IDE Workspace.
Now you mention it, it sorta is; I haven't used eclipse, but I've never stopped putting all of my projects into a single folder called "workspace" in my home directory.
The main difference would be that all dependencies are also in this folder, instead of e.g. a maven or ivy folder located somewhere else. And a (suggested?) folder structure, not dissimilar to java's package structure, where you'd put your e.g. github.com/user/repo repository into $GOPATH/src/github.com/user/repo. I'm sure there's clever tooling or commandline wizardry that works really well with a structure like that.
Having introduced Golang in a business environment in 2014 and having seen the language solve real problems at that time, at scale, I feel that if your own reason to not use Go is GOPATH... Either Go does not solve any real problems for your use case, or your reasons to use Go is misaligned.
GOPATH is really not a problem, but efficient concurrency is. Go made the company I worked for go from 20 deployments for a service to 1 (I/O bound), reducing the costs for that service a lot. If you are willing to let the environment get in between that...
Of course, you could have implemented that system using assembler for all I care, but Go really made it possible within that organization, something Java, C and other languages failed at before Go was introduced.
N=1, ymmv, &c
> something Java, C and other languages failed at before Go was introduced.
Java's concurrency solutions are just as good, if not better than golang's. Pair that with libraries such as RxJava and you're definitely ahead. I don't see what golang has to offer in that area that the JVM doesn't
Sounds believable, but at the time we were stuck at 1.6 and had a significant drop in traffic due to JSSE and non-existing support of "modern" TLS Cipher Suites and TLS extensions (we even had problems with SNI, years after introduction)...
We were using C because epoll made it possible for us to compete, but the Java reactivex stuff were lagging behind.
Moving to a completely different language and platform was easier than moving from Java 6 to Java 8? Why were you stuck on 6 otherwise?
Go may very well have been through best fit for you. However, saying that Java's problem was that you couldn't upgrade from 1.6 yo 1.8 but you could introduce a completely different language doesn't make much sense to me.
Why couldn't you have just put a reverse proxy in front of your java?
But then you have to write java
You don't have to deal with the jvm for one thing. Java for another.
Both of those things are selling points for me.
The problem was never GOPATH itself, but the fact that the restriction made it impossible to manage versions and dependencies in a sane way.
I whole heartedly agree with the author.
While I disagree with GO's style guide and a ton of other choices - I don't mind following them since that's what the language requires. But it goes past that and wants me to organize everything my working directory just for it - it's absolutely crossing a line.
While I didn't stop using Go because of GOPATH, it's one of those things that absolutely annoyed the crap out of me.
to the point that all my go projects are now organized as `project_name/go/src/github.com/username/project_name` as an example.
This was really really annoying to say the least.
Is it really that hard to solve bad UX of GOPATH? When Go 1.0 came out I remember I couldn't care less about Rob Pike's thoughts on GOPATH and GOROOT and just made a tiny wrapper for go tool that was looking for src/ directory in my tree to populate GOPATH, starting with the current directory (sort of like git does with .git/ directory). And for GOROOT I used a path relative to the wrapper itself.
If you are investing time into learning a new language anyway, these things take very little time in comparison and definitely worth it.
That's funny, but since I started using Go, I now organize all projects under GOPATH, i.e. in `~/src/github.com/user/project`, no matter what language it is in. GOPATH and HOME are almost synonims now.
I find it so much better experience than using tons of `~/Work`, `~/Projects`, `~/Code`, `~/SomeLang/` etc. as I used to have before.
I have been using something similar to ~/site/user/project for years and it worked well so far.
me, too. Personal projects:
~/projects/USER/NAMESPACE/PROJECT
Company projects:
~/projects/COMPANY_SLUG/NAMESPACE/PROJECT
If you read over the rest of this thread, you can find opinions ranging from "GOPATH is horrible" to "It's wonderful". And I just want to point out this is pretty good example of how important the marginal improvements can be. Abstractly, you might have a hard time sitting there imagining how for a thousand of your users, improving $ONE_SMALL_FEATURE might flip them from not paying you to paying you, but at scale, yes, such margin-based thinking really is important. Just like you may find it hard to really intuit that going from .75s page rendering to .5s page rendering can have a significant impact on the bottom line for an e-retailer, but, yeah, it does.
Just wanted to point this out as a clear example of such an issue, since it doesn't come up very often so cleanly.
I guess go 1.11 introduced a modules experimental feature as an alternative to `GOPATH`
> Go 1.11 adds preliminary support for a new concept called “modules,” an alternative to GOPATH with integrated support for versioning and package distribution. Using modules, developers are no longer confined to working inside GOPATH, version dependency information is explicit yet lightweight, and builds are more reliable and reproducible. https://golang.org/doc/go1.11#modules
> I have major gripes with PEP-8, and if you ever see me using it I want you to shoot me in the face.
That sounds extreme. I've never heard this before. Why?
IIRC, pep8 would have you write:
def my_very_very_very_very_very_long_function_name(self, param1, param2):Looking at it (https://www.python.org/dev/peps/pep-0008/), that is one of the options, but you could also do:
Shrug. Seems okay to me, and preferable to what they say no to.def my_very_very_very_very_very_long_function_name( self, param1, param2): passYou can choose to use:
def my_very_very_very_very_very_long_function_name( self, param1, param2): ...Ah, my mistake.
My preference is to do this, which I think is quite nice and easy to work with:
def my_very_very_very_very_very_long_function_name( self, param1, param2, ): do_it() # function body here.Yup this. It works perhaps even more nicely in languages that have braces and return types, e.g.
func some_name( arg: Type, arg2: Type ) -> RetVal { … }
Yep, this is by far my least favorite autopep8 "correction".
if your function name is that long, you have a problem
I have major gripes with it too and don't use it unless a project enforces it.
The main ones that get me are the whitespace (4 characters) and restrictive line length (79 characters) rules. They don't work well together, and also don't work well with python's significant whitespace (can't easily break up a line), namespaces, generator expressions, and type hints. All for a subjective formatting decision.
I'll also often see people trying to cram as much as they can into 79 characters with one letter variable names and such while calling that elegant code because it's a "one-liner."
Yes, the 79 character limit gets in my nerves. Unless you're using a machine built in the 70s or 80s, this limit is complete BS
To be honest it says right at the beginning of PEP-8 that "A Foolish Consistency is the Hobgoblin of Little Minds" but it seems little minds are the norm (and usage of 'pep8' package to make developers do what a computer could do faster and better)
I don't see many projects enforcing a line length, and if they do it's generally set much higher (120-150).
PEP-8, to quote it [1], says in its first sentence:
> This document gives coding conventions for the Python code comprising the standard library in the main Python distribution.
If you are not writing Python's stdlib, you are free to not to stick to it.
If in doubt, the next section of the document is helpfully titled "A Foolish Consistency is the Hobgoblin of Little Minds".
My biggest problem with Go is the lack of a constructor. I don't feel that a struct type is good enough to enforce data integrity and as you have a more complex program, you need to know that the object you are being passed has data integrity.
With a constructor, I can force data to conform to what I need it to. I can ensure that certain fields are not nil, that they conform to a specific list of values, etc.
Because of a lack of a constructor in Go, I can't do that and I need to continuously validate the data, which is annoying and a source of bugs. It can be said that this can be accomplished with interfaces but that's adding a lot of complexity to something that should be a lot easier to handle, in my opinion.
By authoring private structs with public constructors, you can get what you want out of this. Gets a bit messy with testing, but not impossible.
If you have your tests within the same package, they can access the private members of structs directly, which helps avoid some of the mess.
If you’re in a different package things aren’t too bad if you have meaningfully abstracted interfaces. And with Go’s duck typing, even if you’re using another package that doesn’t have good abstraction, you can create your own interfaces with the functionality you need for mocking purposes.
You're getting downvoted here, but I agree with you. Once you really lean on Java constructors to guarantee initialized state, it's hard to go to something like Go that doesn't have this. Sure, you could do a lot of gymnastics to try to work around it, but that's the thing about Java that's nice. It's just built-in with that in mind.
Having written a lot of C++, constructors are a bad idea. Two main reasons:
1. Error handling is difficult - they don't return anything, so you are forced to use exceptions, and throwing exceptions in constructors is awkward. Go doesn't even have exceptions so it can't do that.
2. They don't have names. Often this is fine - you only have one way to construct an object. But if you have more than one, then you have two unnamed functions that do different things. And if they have the same parameters you end up with crazy workarounds like adding dummy parameters.
The way Rust does it is far superior. Basically you have a static function that creates the object. It's a normal function, so it can return errors (or the object), and you can name it, so you can have `Circle::new_with_radius(float r)` and `Circle::new_with_diameter(float d)` with no confusion.
Much better.
How about just using the builder pattern?
Not familiar with go or that pattern but is this a good example? https://gist.github.com/vaskoz/10073335
Yes, that's a good example
have an initSomething() that returns an initialized struct.
How is this any different than namespace_init(); functions from C?
I hear language complaints like this all the time, and it always sounds like developers grasping for something to complain about.
I'm not even a Go fan, but I think you can cut the language some slack here at least in this department.
The entire approach Go is selling from what I've hear is they're entirely forgoing the traditional object-oriented (in the C++ and Java sense of the term) approach for composability. Okay, sure, whatever, good for Go developers; but you can't complain about a leopard having spots. Just accept it or move on.
> How is this any different than namespace_init(); functions from C?
It's not; presumably that's the point. C isn't exactly famous for its support for data hiding and enforcing invariants.
I have used goinstall, go get (in GOPATH), godep, dep, multiple GOPATHs, symlinks, git submodules and lots more to figure out dependencies in Go, and modules is FINALLY something that actually makes sense.
GOPATH issues - Elaborate more?
Your go code has to live in go's directory structure. If your project is github.com/jrockway/whatever, then your source code must live in ~/go/src/github.com/jrockway/whatever/<go files>. There is no other place you can put it (though you can change ~/go to something else by setting $GOPATH in your environment).
I assume what upsets people is that everything in go is in a global namespace. So if you have ~/foo-project with some go in it, that doesn't work; you can't import things from there and the compiler won't build it. Instead you have to position it in the "global" ~/go/src for anything to work.
I used to teach Perl classes and people were equally upset about the concept of @INC. They did not want to manage packages that way, and the programming language did not give them a choice. This is very off-putting to some people.
(Me, I don't care. Not having 8000 configuration options to let the compiler find some github project I'm using is wonderful, even if having to cd go; cd src; cd github.com; cd jrockway; cd project; is kind of a lot of typing to get to the thing you're working on. I have a bash alias to get there ;)
One nit: it's not "global" because it comes from $GOPATH. If you want, you can create a project like this: `~/myproject/src/app/main.go` and it will work fine so long as you set `GOPATH=$HOME/myproject` for your current shell.
It's not that trivial in my experience. I tried hacking my shell so that the `go` command would automatically set GOPATH for me, but then I also had to set GOBIN for some reason, and even then I had to move my repository from ~/code/myproject to ~/code/myproject/src/github.com/myuser/myproject - which is just hugely unnecessary.
> cd go; cd src; cd github.com; cd jrockway; cd project; is kind of a lot of typing to get to the thing you're working on. I have a bash alias to get there
In addition to your bash alias for navigation, you can use the `autocd` option of bash so that from your home directory you can say `cd /project`.
You can also use the CDPATH variable for quick navigation into a deep directory structure like that.
Go insists that all of your Go code must live inside of a single hierarchy under $GOPATH. This pretty much forces you to organise files in your system in a very particular way, where the language a project is written in takes precedence over any other organisational concerns, and doesn't play well with, e.g. my setup, where I file my projects in a hierarchy shaped like ~/dev/{personal,$COMPANY,3rdparty}/$PROJECT_NAME.
Symlinks are your friends ;)
Keep the actual directory where Go wants it to be and create a symlink to it in ~/dev/$who/$project.
Next, create a script that you name as “mygo” or whatever (something short and memorable that makes sense to you. I would probably name it as just “g”) and put it in your ~/bin/ and ensure you have ~/bin in your $PATH.
In said script you resolve the real path of your project, cd there and then execute /usr/local/bin/go with the args that your script got:
So when you are in ~/dev/someclient/someproject/, you run “mygo build” and the script runs “go build” from the real path of the project. (At first I suggested to name your script as just “go”, but I decided that it was probably better to use a non-colliding name instead and so I quickly edited this comment.)#!/usr/bin/env bash cd "$( realpath . )" /usr/local/bin/go "$@"That ought to do it.
I totally agree with you though. I do similar to you — I keep public projects under ~/src/github.com/ctsrc/$project and client projects under ~/src/$client/$project. If it wasn’t for the fact that I don’t write in Go I would be annoyed too.
Sure, there's decent workarounds to solve this problem, but I take it as a general rule that, if I'm fighting against (rather than with) a tool, I'm either using the wrong tool, or I'm using the tool wrong.
If my fight against a language starts at the "I can't put my source code where I want without having to fight it", it's an uphill battle to convince me this is not a "using the wrong tool" scenario, and I'm happy to take my dev time elsewhere.
I have multiple different languages and code for multiple companies and have no issues. I guess the issue is that you don't want to include the repo location in the path?
I mean I want to have ~/dev/personal/some_go_project and ~/dev/personal/some_java_project side by side, instead of being forced to move some_go_project to ~/go_dev/some_go_project. Java (and, well, just about every other language I've ever touched) is perfectly ok with this, Go isn't.
I was about to ask the same, then after some thinking about it, came to the conclusion that he doesn't like to be forced to save all Go libraries under $GOPATH. Now with Go 1.11 you can have "modules" that you can put all over the place. Much more freedom, right? :)
Apparently too lazy to set an environment variable. I found GOPATH annoying but not using Golang simply for that seems absurd to me.
You see, GOPATH crossed a line. Go is opinionated, which is fine, but with GOPATH its opinions extended beyond my Go work and into the rest of my system. As a naive new Go user, I was prepared to accept their opinions on faith - but only within their domain. I already have opinions about how to use my computer. I knew Go was cool, but it could be the second coming of Christ, and so long as it was annoying to use and didn’t integrate with my workflow, I (rightfully) wouldn’t care.
That's not the point: https://news.ycombinator.com/item?id=18178987
And just what point is that? The article states that "You see, GOPATH crossed a line. " Doesn't explain what that line is and then goes on to say that Golang is an awesome and simple language.
He says it in the next sentence.
"...but with GOPATH its opinions extended beyond my Go work and into the rest of my system. [...] I already have opinions about how to use my computer."
Not sure I agree with that statement. Yes Golang has an opinion on where it wants you to keep your go code but it dosen't effect the rest of your system, just your go work.
Where I want to keep my code is not one of the things I grant the language the right to dictate - even if the code is written in that language.
Sure, but there are 100 other languages out there that do the same thing as Go but don't have an opinion about the organization of my system. I'd rather just use one of those, and really I don't think I'm missing out on much by not using Go.
Toggling GOPATH between projects is a huge pain, it's not as simple as set it once and forget it.
But how did Go solve the GOPATH problem? He didn't say.
Go modules
FTA: "Go modules are great, and probably the single best module system I’ve used in any programming language. Go 1.11 took my biggest complaint and turned it into one of my biggest compliments."
I actually adopted GOPATH for everything. I use https://github.com/motemen/ghq to quickly clone non-Go projects into the ~/src/github.com/user/project style paths.
But… I don't like Go itself (anymore).
It's not just the 'if err != nil', it's more the general attitude/philosophy from which it comes. Go is anti-intellectual, the designers basically don't respect the user. "You're too stupid to use generics/Result<A,B>/monads/whatever" is what it feels like they think of you.
Go internals are more infuriating than Go language though…
Especially the Plan9-based custom assembler. It truly is a horrific atrocity. I've had to deal with it two times:
1. That assembler does not support all instructions and even addressing modes (!) of amd64, so people have had to create hacks like https://github.com/minio/c2goasm to run asm functions without the overhead of cgo. Please actually read that project's README, but tl;dr it assembles your code using a normal assembler and STICKS THE BINARY CODE INTO A GO ASSEMBLY FILE AS HEX CONSTANTS. This hack actually didn't work for me when I tried to use some SIMD code, so I had to resort to cgo with its call overhead.
2. I also tried (and failed) to port the Go runtime to FreeBSD/aarch64. This was the last straw, this is what made me actually hate Go.
Side note: I really liked the design of his blog.
Yes, but how to do error handling correctly with Go? My Go code crashes.
Server is down?