Google's new language Go tries to make dependencies management easier by explicitly requiring that all dependencies listed in a module actually be used. The compiler will reject a module that declares a dependency to a module without using anything from that module.
It is illegal for a package to import itself or to import a package without referring to any of its exported identifiers.
I can think of some obvious advantages (e.g. cleaner modules) but maybe there are some non-obvious ones. The only disadvantage I can think of is having an overly pedantic compiler, complaining too much during refactoring, but maybe there are more?
Do you have any experience with other languages enforcing this? What are the pros and cons of this approach?
A go. mod file is required for the main module, and for any replacement module specified with a local file path. However, a module that lacks an explicit go. mod file may still be required as a dependency, or used as a replacement specified with a module path and version; see Compatibility with non-module repositories.
As of Go 1.11, the go command (go build, go run, and go test) automatically checks and adds dependencies required for imports as long as the current directory or any parent directory has a go. mod.
Go is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. It is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.
Not only you need to explicitly use all dependencies, but also all variables must be used. The compiler will give you errors when you have unused variables.
They are annoying. But it will make others happy because they get clean code.
I'm thinking that probably Go designers intended Go to be a language that is largely IDE dependent.
I'm guessing that the biggest motivation for this, and the biggest result, is an improvement in compile times. The tech preview video made a major point of their ability to compile large amounts of code in short time periods (their example was 200,000 lines of code in 8 seconds on a MacBook - no machine specs given).
Also in the tech video, they specifically mention that one of the biggest ways they achieved that was in changing the way modules were compiled and linked.
Here's an example of how something would work in a current C/C++ system:
Class A is defined in header file C_H and implemented in C_CPP. Class B derives from C and is implemented in files B_H and B_CPP. Class A derives from B and is implemented in files A_H and A_CPP.
Because of the derivation chains, A_CPP includes A_H, B_H, and C_H. B_CPP includes B_H and C_H. C_CPP includes C_H. Due to the nature of the C preprocessor, which essentially turns a #include into a cut and paste operation, the contents of C_H are run through the compiler 3 times and B_H is run through twice.
Further, the contents of A_CPP, B_CPP, and C_CPP all live in their own object files. Hence when the linker goes to resolve A.o, it is forced to load and process both B.o and C.o. Also, when it resolves B.o, it has to process C.o again.
Precompiled headers can help quite a bit with the first part of this problem, but they also can be a royal pain to maintain and I know a lot of developers who simply don't use them for that reason. It also doesn't fundamentally change the problem - the headers are still processed at several levels multiple times, only now a precompiled binary is processed instead of the source. Several steps are cut out, but not the entire process.
Go approaches things differently. In the words straight out of the PDF from their tech talk:
"The Go compiler pulls transitive dependency type info from the object file - but only what it needs. If A.go depends on B.go depends on C.go: - compile C.go, B.go, then A.go. - to compile A.go, compiler reads B.o not C.o. At scale, this can be a huge speedup."
OK, slight tangent is done. Why is that relevant? The answer is also in the Go Tech Talk PDF:
"Package model: Explicit dependencies to enable faster builds."
I'm guessing that the Go developers took the stance that when compile times are measured in seconds, even for very large projects, that it's more productive for developers to keep compile times that short. Say it takes me 8 seconds to compile 200,000 lines of code and discover that I've got an extraneous package import, 5-10 seconds (with a good IDE, or good familiarity with your dev environment) to find it and fix it, and another 8 seconds to recompile. Call it 30 seconds total - and now all of my future compiles stay in the 10 second range. Or we can let our module grow by including unnecessary dependencies, and watch that compile time creep up from 8 to 10, 12, or 15 seconds. It doesn't seem like much because we're all used to compile times on the order of minutes - but when you start to realize that that's a 25% performance degradation, you stop and think about it for a minute.
Go compile times are already lightning fast. Consider also that processor speeds are still increasing (if not as much as in the past) and that the number of cores available is also increasing (and compiling large amounts of code is well suited to multi-threading). 200,000 lines of code in 8 seconds today means that it's not unreasonable to imagine 200,000 lines of code compiling essentially instantaneously in 10 years. I think the Go team has made a conscious decision here to make compile times a thing of the past, and while the issue you bring up is only a very small part of that, it is still a part of that.
On another note entirely, the Go team also seems to have developed a philosophy of a language design that forces some good programming practices. To their credit, they've made the effort to achieve that without serious performance penalties, and largely succeeded. [Aside: The only two things I can think of offhand that actually impact performance are garbage collection and forcibly initialized variables - and the latter is pretty trivial in this day and age.] This is going to royally irritate some programmers, while making others happy. It's an old, old argument in the programming world, and it's pretty clear which side Go has come down on, like it or not.
I think the two forces together influenced their decision, and I think that at the end of the day it's a good way to go, although I support other commenters here who have suggested allowing a "--strict" flag or some such to make this particular behavior optional, particularly during the early stages of a project's lifecycle. I could easily see myself defining variables or including packages when I first start writing code that I know I will need later, even though I haven't yet written the code that needs them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With