|
For every "quirk" mentioned in this article there are very good reasons for it, which the author lacks the imagination or patience to try and reason through. Point 4 is perhaps the best example. So given an interface Foo and a struct FooImpl which implements Foo, why can't we pass a slice of []FooImpls to []Foo? It's because the creators of Go are lazy and arrogant and incompetent and don't care right? No. Consider the situation where a []FooImpl is passed to a function F(f []Foo). This function decides to change one of the elements of the slice to a different implementation, say FooImpl2, eg. f[0] = FooImpl2{}. Now the original slice of []FooImpl's has a FooImpl2 in it. Oops, we broke type safety. Now consider what it would take to make this work - you'd have to somehow guarantee that functions which take []Foo don't mutate the slice. Is that possible? Is it possible to do quickly, and still have a language that compiles very quickly? Or we could introduce "const" and all those things from C++ but that's a whole new level of complexity to the language. And what about the performance penalties? A struct is a different size than an interface, so what code is emitted during compilation, something that can handle iterating over []your_interface and []your_struct? So next time you think "Gosh, this bit of Go really sucks" take a moment and think about why it was designed that way. Because the fact is, it was almost definitely designed that way, as opposed to just overlooked or neglected. You may not agree with the choice that was made, but with the people who work on Go, I can guarantee that there was a conscious choice that was agonized over and discussed endlessly, just not with you in the room. |
Consider the block against unused imports. Now consider an application made of hundreds of .go files, that relies on hundreds of libraries, each of which could itself be made of hundreds of .go files---all of which changes frequently enough that a total recompile may be necessary often (which might sound like a code-base you can imagine Mr. Pike would be familiar with). It's actually a non-trivial amount of work to churn through and ignore all the unneeded imports.
"But that's sacrificing developer prototyping velocity to solve a problem that most developers never see; at most, it should be a feature toggled by a compiler flag", one might argue. Well, sure... Now consider how many C / C++ libraries cannot be compiled with -Wall -Werror because the original developer had the option of building the library without those flags enabled and not enough people want to learn all the subtle details of the languages they use, they just want "the code to compile and be done with it." Now consider what that does to the problem of trying to build larger projects from these tiny pieces that were never actually sanitized for unneeded imports... Which will inevitably happen to code that works well enough, etc.
It's a line drawn in the sand, but it's a line drawn in the sand from hard-won experience with how little projects grow into big projects.
Besides, the language is so small that it's not very hard to write wrappers for IDEs that can quickly identify and kill unused imports automatically (probably also add them when needed, like the most common "oh GOD I need to put 'fmt' back in just because I'm pen-testing the outputs in this code I'm debugging").