Recently, with the release of Rust 1.0 and 1.1, this article has come back to poison readers.
So I've written this rebuttal to point out the problems.
There are several times when I think about it, I always ask myself: why did I give up Go? Was it the right decision? Was it wise and rational? I've actually been giving this question some serious thought.
To put it bluntly, I gave up on Go (golang) because of two things: first, I was unhappy with the language itself, and second, I was unhappy with some of the people in the Go community. No doubt, this is a very subjective conclusion. But I have enough detailed and objective arguments to support this seemingly subjective conclusion.
At the end of this article is an update log for this article.
It is indeed a very subjective conclusion, because there are quite a few questionable points in it (which are fine for fooling Go-heads).
Section 0: My Go Language Experience
Let's start with my experience, to avoid being dismissed as a low-level Go language hack for no reason.
The first public version of the Go language (golang) was released at the end of 2009, and with the aura of "made by Google", it attracted a lot of tasters, and I (Liigo) was among them, so I read some general information about Go, studied the basic tutorials, and learned a lot about its syntax. I read some Go materials in general, studied the basic tutorials, and quickly forgot about it because I was dissatisfied with the semicolons and curly braces in its syntax, and didn't take it seriously.
When Go was first released in 2009, it did attract (both the author of the article and many IT journalists) a lot of low-level testers because of its "made by Google" aura.
Fortunately, after 5 years of development, not many of these speculators remain (Google Trends) purely because of the aura.
By now, the real Go users are already using Go in a productive way.
When it comes to the semicolons and curly braces in Go's syntax, I'd say that's just your personal opinion, and that there are plenty of people who are happy with Go's semicolons and curly braces,
including the language designers of Fruit's Swift (which has essentially the same semicolons and curly braces as Go).
If it's just a matter of personal opinion, I'd also say that Rust's fn acronym is a pain in the ass!
Two years later, at the end of 2011, when the Go language 1.0 release was on the agenda and there was a lot of coverage of it, I paid attention to it again, re-evaluated it, and decided to get y involved with Go. I subscribed to its users, nuts, dev, commits, and other official mail groups, and insisted on reading the emails every day, as well as every source code update submitted by the developers, and submitted a lot of improvement ideas for Go, even including modifying the source code of the Go language compiler to participate in the development tasks directly. This went on for months.
This is true, and there's quite a bit of bickering going on at golang-china, so dig in if you're interested, and I won't expand on it.
By the time Go 1.0 was released in early 2012, both the language and the standard library were basically finalized, and it was impossible to improve them significantly. I was very disappointed that the Go language was not able to make a breakthrough before 1.0 was finalized, and even went to 1.0 with a lot of obvious flaws, and I gradually distanced myself from it (so I seldom cared about what happened after Go 1.0). Later, I saw the Release Note of Go 1.1, and found that there is no big change in the language level, but only some fixes and improvements in the library and tool level, and I felt that it has lost the power to grow when it is still young, and I was more and more disappointed. In addition, some people in the Go community, including some of the people at Google who are responsible for developing Go, have attitudes, words, and actions that I find extremely distasteful, and that have led me to abandon Go.
It's really not clear what major improvements and obvious flaws the owner is talking about that could have been realized in the short time before Go 1.0.
If you're talking about major improvements such as the lack of semicolons and parentheses in Go's syntax, I can only say that it's just your personal subjective feelings,
and a lot of what you think can only be convincing to yourself, but not to the majority of other people (don't think that having all the features in Go like C++ or Rust is a bad idea, and that all the features you have are just a bunch of bad ideas. It's not like it's a silver bullet).
Go 1.1 Release Note, found that the language level has not changed much. The reason for the lack of changes at the language level is Go1's commitment to backward compatibility. For an industrial-grade language, Go1 can only be a benefit. Who would dare to use Go for production development if the language layer was drastically improved with each release (I recognize that Rust's changes are bold, but they also show that Rust is still relatively naive and capricious)?
I agree that some people in the Go community are stubborn. But these stubborn people can be reasoned with, but they are very demanding about a lot of things (especially about Go's design philosophy).
As long as the advice you give is valid (the design philosophy of the language is a different matter), they will not blindly reject it (it will just take longer to discuss).
Regarding the article on adding BOMs to Go files, I'd like to add a note.
When Go 1.0 was released, Go source files (.go) were explicitly required to be UTF8-encoded, and BOM-free UTF8-encoded.
Note: This BOM-free UTF8 restriction applies only to Go source files (.go).
This restriction does not mean that users are not allowed to work with UTF8 txt files with a BOM!
I don't see any problem with this restriction for writing Go programs, and so far, I've never worked with .go files with BOM.
Not only does a .go file with a BOM not make much sense, but it has a lot of flaws.
The BOM was originally intended to indicate whether an encoding was big-endian or small-endian, and was used mainly in UTF16 and UTF32. For UTF8, the BOM had no purpose (it was the two Go authors who invented UTF8, which completely solved the global encoding problem).
But, in reality, because of MS's txt notepad, for Chinese environments, txt (and even C/C++ source files) will be treated as GBK encoding (GBK is a lousy encoding),
In order to differentiate whether it is GBK or UTF8, MS's notepad adds BOM in front of this garbage (which is taken up by GBK), and the bom is not meant to indicate byte order anymore. is not the byte order of the original intention. I don't know if anyone has ever written a web page in MS Notepad, and then generated a utf8 web page with a BOM, it would be very interesting.
This is a bug in MS Notepad: it doesn't support generating UTF8-encoded text files without a BOM!
These are real UTF8-encoded text files with BOMs, but they are certainly not Go source files!
So there's nothing wrong with Go source files even if they have a mandatory restriction on BOM-free UTF8 encoding (and I'd like to have that restriction).
Although Go source files now accept UTF8 with BOM, running go fmt still removes the BOM (because the BOM is nothing). In other words, Go source files with BOMs do not conform to the Go coding style, and go fmt forces the removal of the BOM header.
It was said that the BOM is a piece of MS garbage, but the UTF8 BOM has a lot more problems than just being a piece of garbage, because the BOM embeds garbage in the beginning of the string,
causing regular expressions, string linking, and so on, to be polluted by the BOM garbage. For .go, even if the code is identical, having the BOM and not having the BOM will result in different MD5 and other checksums for the files.
So, I don't think Go users need to worry about the BOM.
In the last decade, I (Liigo) was y involved in the development of two programming language projects in the company I belonged to. I guess I should still have a little say in how to judge the merits of a programming language, or at least how to judge whether a programming language is suitable for myself.
Section 1: Why I'm not thrilled with Go?
There are a lot of things that irk me about Go, and here's a list of some of them that I can remember right now, in basically no particular order. After you've patiently read through them, can you say "I don't care"?
1.1 No left braces on a separate line
The placement of braces has been the subject of ongoing controversy in the C, C++, Java, and C# communities for more than a decade, and there has never been a consensus. In my opinion, this is a highly subjective choice, and it should not be a one-size-fits-all decision without violating principles or involving right and wrong; it is enough to let programmers or teams make their own choices. Programming language itself to impose restrictions, to impose their own preferences on others, the loss is not worth the gain. No matter which of them one prefers, one is bound to offend a group of people who are in opposition to it. I've gotten used to putting left parentheses at the end of a line, but the thought of being forbidden from choosing another option makes me feel very unhappy, and the Go language is failing to "unite all the forces that can be united" on this issue, as well as intentionally making enemies of its own, which is a failure.
I think the greatest invention of Go is go fmt, from now on Go users won't have to spend the position of brackets this kind of boring argument (of course, also a lot less water and on the tiobe rankings of the opportunity).
The advantage of this is that the Swift language uses a similar style to Go (although the author of swift is probably despised).
1.2 The compiler inexplicably puts a semicolon at the end of a line
For Go itself, the semicolon at the end of a line can be omitted. However, the compiler (gc) implementation forces the semicolon at the end of the line during the lexical analysis phase for the convenience of the compiler developer, which in turn affects the language specification, which makes special rules about "how to add a semicolon". This perversion is unprecedented. The semicolon it automatically adds at the end of the previous line when the left curly brace is accidentally placed at the beginning of the next line causes inexplicable compilation errors (pre-Go 1.0) that even it can't explain. If you really can't handle semicolons, why don't you just not omit them; alternatively, the compilers for Scala and JavaScript are open-source, so can you learn from them how to handle omitted end-of-line semicolons?
Again, it's a personal opinion, but I like this feature. Swift is similar.
1.3 Emphasizing compilation speed at the expense of functionality
Programmers are human, not gods, and they can't afford to make mistakes in coding, either through carelessness or negligence. Some of these mistakes are very easy to make collectively (I can't think of any examples in Go at the moment, but in C++ there's the "base class destructor is not a virtual function" example). This is where the compiler should step in and do some checking, constraining, and checking to try to prevent routine errors from occurring, to try to prevent potentially buggy code from compiling, and to give warnings or hints if necessary so that the programmer can be aware of them. Compiler is not a machine, is not it should do more dirty work miscellaneous work, reduce the burden on the human mind? One more check by the compiler may prevent hundreds of thousands of programmers from making the same mistake countless times in the years to come, saving countless hours of time, which is a great thing. But the authors of the Go compiler don't see it that way; they don't want to spend hours adding new features to the compiler themselves, feeling that they're losing money and slowing down the compilation instead. They rejected a lot of requests for improvements to the compiler on the grounds that they would affect compilation speed. Typical of choking on the compiler. While the emphasis on compilation speed is appreciated, I'm not in favor of giving up features that should be available as a result.
Compilation speed is important, and if it's slow enough, no one will use the language if it's good enough.
For example, incremental compilation/pre-compiled headers/concurrent compilation in C/C++ are all about speed.
Rust 1.1 also claims a 32% reduction in compile time over 1.0 (note: not runtime speed).
Of course, when Go first came out, compilation speed was one of the design goals.
But I think what you're probably talking about is the problem of compilation errors caused by compilers adding semicolons themselves.
I think it's a language feature that { can't start a new line in Go, and fixing it would introduce a new bug.
I can't think of any other bugs that would speed up compilation at the expense of providing functionality that should be available (and don't mention generics, because they're not yet well-designed).
1.4 Error handling is primitive
The basic pattern of error handling in Go is that a function usually returns multiple values, the last of which is of type error, which is extremely descriptive of the type of error; the caller needs to check for this error every time a function is called and handle the error accordingly: if err ! = nil { /* don't want to throw up after writing too much of this code */ }. This pattern is the same as the very primitive error handling in C, and is not a substantial improvement. In practice, it is easy to form a multi-layer nested if else statement, think of this coding scenario: first determine whether the file exists, if it exists, then open the file, if it opens successfully, then read the file, if it reads successfully and then write a piece of data, and finally close the file, do not forget to deal with the case of each step in the event of an error, the code written out of the more perverted, how ugly? In practice, the common practice is to determine the operation of the error return in advance to avoid the nesting of multi-layer braces, but the consequences of doing so is that many of the error handling code is placed in front of the position of prominence, the conventional processing logic is buried in the back to go, the code is extremely poor readability. Moreover, the standard interface to the error object can only return an error text, and sometimes the caller even needs to parse the text in order to distinguish between different error types. On top of that, you can only manually force the conversion of the error type to a specific subtype (there goes the advantage of static typing). As for the panic - recover mechanism, the fatal flaw is that it can't be used across library boundaries, and is destined to be a half-baked product that can only be played with in your own pkg at best. Java's exception handling, while having its own problems (e.g., Checked Exceptions), is still much superior to Go's error handling in general.
That said, software development has evolved over the past half-century, and still hasn't improved substantially. Don't think it's revolutionary to have a syntactic sugar for exceptions.
All I can say is that errors and exceptions are two different things, and treating all errors as exceptions is SB behavior.
Because of the so-called silver bullet of exceptions, there is a lot of waiting for others to help wipe their asses (note that the shit function throws more than one type of shit, and the various xxx_shits called indirectly by it may throw various types of exceptions, which leads to catch out of control):
int main() {
try {
shit();
} catch( /* How many thousands of shits are there ? */) {
...
}
}
Go's recommendation is that panic - recover does not cross boundaries, i.e., it requires that normal errors be handled by the pkg.
This is responsible behavior.
And since Go is a concurrency-oriented programming language, doesn't it feel wrong to use try/catch in a massive goroutine?
1.5 The Garbage Collector (GC) is imperfect and has major flaws
In the run-up to Go 1.0, the GC had a memory leak in 32-bit environments, and has been dragging its feet on improving it, not to mention the fact that the real fatal flaw of the Go GC is that it can cause unpredictable and intermittent halts of the entire process. Like some large background service programs, such as game servers, APP containers, etc., due to the huge amount of memory occupied, the number of memory objects is extremely large, the GC to complete a recovery cycle, it may take a few seconds or even longer, during this period of time, the entire service process is blocked, stagnant, in the eyes of the outside world, is the service is interrupted, unresponsive, and then awesome concurrency mechanism to the failure of all here. The garbage collector is started periodically, and each time it is started, it causes a short service interruption, so at this rate, does anyone dare to use it? This is a background server process, which is a key application area for Go. The above phenomenon is not my hypothesis, but the existence of real problems, by its serious distress is not one or two (the end of 2013 ECUG Con 2013, Jingdong's Liu Qi mentioned Go's GC, defer, the standard library implementation is a performance killer, the biggest pain is the GC; the United States of America's Shen Feng also mentioned that Go's GC led to intermittent stops in the background services are the the biggest problem. (The development team of the earlier MMO Immortal Way was also hit hard by Go garbage collection.) In practice, you have to try to reduce the number of objects in a process in order to keep GC-induced intermittent stalls within acceptable limits. You don't have any other choice (do you want to change the GC algorithm yourself, or even cut GC? Is that still Go?). GC is still a Go language. Thinking outside the box, I've been thinking lately, do we need a garbage collector? Is not having a garbage collector necessarily a step backward in history? (Might write a new blog post on the topic.)
This is about 32-bit systems, which is definitely not the focus of the Go language's application area!!!! I'd say Go was born for 64-bit systems and multi-core CPU environments. (Besides, Rust doesn't seem to support XP at the moment, so could that be considered a big deal?)
32-bit was a problem at the time, but it didn't have much of an impact on production (are you still using a 32-bit system, with only 4GB of RAM installed). If it is an 8-bit microcontroller environment, it is recommended not to use the Go language, direct C language is good.
And this problem is long gone (see the Go release notes).
Go was born only 5 years ago, GC refinement and improvement is a continuous work, in August 2015 will be released Go1.5 will use parallel GC.
One of the criticisms about GC is that it can lead to lag, but I think this is mainly due to the implementation of the GC is not perfect and lead to.
If GC is perfectly concurrent and incremental, it shouldn't be a big lag problem.
Of course, if you want real-time, use C (real-time doesn't mean high performance, just manageable response times).
With languages like Rust that don't have GC, it's almost impossible to develop concurrent backend programs easily.
Don't just keep blowing off Rust as a replacement for bottom/middle/top tier development, let's look at what anyone has actually done with Rust.
1.6 Disallowing unused variables and redundant imports
The Go compiler does not allow unused variables and redundant imports, and if they do exist, they will result in compilation errors. But the reality is, in the code writing, refactoring, debugging process, for example, temporary comment out a line of code, it is easy to lead to both unused variables and redundant import, direct compilation errors, you must accordingly comment out the definition of the variable, and then turn the page back to the beginning of the file to the redundant import is also commented out, ... ...and then when you're done, you have to go through several more troublesome steps to get back the code you just commented out. There is also a painful problem, write database-related code, if you import a database driver pkg, it compiles to you to report an error, saying that you do not need to import the unused pkg; but if you listen to the words of the compiler to delete the import, the compilation is passed, the runtime is bound to report an error, said that the database driver can not be found; look at programmers are tossed! not a person on both sides, and finally had to ask the gods: import _. To deal with this kind of problem, a better solution is to regard it as a compilation warning rather than a compilation error. But the Go developers are stubborn and won't allow that compromise.
All I can say about this issue is that the owner's trolling is really not level-headed.
Why don't you use errors instead of warnings? This is to eliminate low-level bugs in the compilation stage (you can think of what is the use of so many warnings in C/C++).
And, import has side effects even if it's not used, because import causes init and global variables to be initialized.
Why perform init and initialize global variables if some code doesn't use them?
If the variables were added for debugging purposes, wouldn't it be normal to remove them after debugging?
If you have to import packages like fmt or log for debugging purposes, and removing the debugging code leads to import errors,
doesn't the owner know how to wrap similar debugging-assisting functions in a separate file?
import (
"fmt"
"log"
)
func logf(format string, a . .interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt. Stderr, format, a...)
}
func fatalf(format string, a ... .interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt. Stderr, format, a...)
os.Exit(1)
}
Import _ is a usage with a clear behavior, which is to execute functions such as init in the package (which can do some registration).
It's a Go philosophy to treat warnings as errors, which of course seems idiotic to the owner.
1.7 Creating Objects in Too Many Ways
Which of the many ways to create an object, calling the new function, calling the make function, calling the New method, or initializing a structure directly with the curly bracket syntax, do you choose? It's not a good choice, because there is no fixed pattern. In practice, if you want to create an object of a built-in type (e.g., channel, map), you usually use the make function to create it; if you want to create an object of a type defined by the standard library or a third-party library, you first have to go to the documentation to find out if there is a New method, and if there is one, it is better to create the object by invoking the New method, and if there is no New method, you have to settle for the second best option, which is to create the object by initializing the structure. If there is no New method, then the next best thing to do is to initialize the object with a structure. This is quite a convoluted process, unlike C++, Java, and C# where you can just new.
C++'s new is shit. The problem that new causes is inconsistent behavior between constructors and normal functions, and there's really nothing superior about this patch feature.
I prefer C's fopen and malloc constructors, which are just normal functions, as they are in Go.
In C++, in addition to constructors being incompatible with normal functions, destructors are also incompatible with normal functions. This introduces a number of pitfalls.
1.8 Objects without constructors and destructors
The absence of a constructor is fine; after all, there's the custom New method, which is roughly equivalent to a constructor. Without a destructor, it's harder to implement RAII, and the extra manual work of cleaning up resources definitely adds to the programmer's mental load. C++ has a destructor, Java doesn't have a destructor but it has a finally statement, and Go has nothing. Yes, you have a defer, but that defer is a bigger problem, see below.
Defer overrides the behavior of the destructor, but of course defer has other tasks. Swift 2.0 also introduces a simplified version of the defer feature.
1.9 The semantics of the defer statement don't make a lot of sense
The Go language was designed with the intention of placing the "code" for releasing a resource close to the creation of the resource, but postponing the "action" of releasing it (deferring it) is a good idea. But the "action" of releasing resources is postponed (deferred) until the function returns. Unfortunately, the timing of the execution seems to be a bit unreasonable. Imagine a function that needs to run for a long time, with an infinite loop statement that keeps creating resources (or allocating memory) inside the loop, and uses the defer statement to ensure that they are released. Since the function keeps running without returning, all the defer statements are not executed, and the large number of transient resources created during the loop keeps accumulating without being reclaimed. Moreover, the system has to take up additional resources to store the defer list, which also continues to grow. At this rate, it won't be long before the system crashes due to resource exhaustion. Like these long-running functions, http.ListenAndServe() is a typical example. In the Go-focused application area, it can be said that almost every background service program is bound to have such a class of functions, and often they are the core part of the program. If a programmer accidentally uses a defer statement in one of these functions, the consequences can be endless. Wouldn't it have been better if the language designers had set the semantics of defer to be executed at the end of the block it belongs to (rather than when the function returns)? However, Go 1.0 has already been released and it's impossible to change it for backward compatibility. Be careful with the defer statement! If you're not careful, you'll get hit.
As mentioned earlier, defer has another task, which is that the recover executed in defer catches exceptions thrown by panic.
And defer can modify the named return value after the return.
The above two tasks require that defer be executed only when the function exits.
What you're saying about defer is that it's similar to the behavior of defer in Swift 2.0, but defer in Swift 2.0 doesn't have either of those features.
Go's defer is triggered by the scope of the function, which can lead to the misuse of for as described by the author (what language doesn't have its pitfalls?). .
But there is a way around partial defer in for (Go's defer is function scoped):
for {
func(){
f, err := os.Open(...)
defer f.Close()
}()
}
Just make a closure function in for. Don't blame someone for not telling you if you don't know how to use it.
1.10 Many language built-in facilities don't support user-defined types
for in, make, range, channel, map, etc. all support only language built-in types, not user-defined types(?). User-defined types (?) are not supported. User-defined types can't support for in loops, you can't write functions with variable "type and number of arguments" or even "type and number of return values" like make and range, and you can't write generic-like data types like channel and map. You can't write generic-like data types like channel or map. The language's built-in stuff is full of axes to grind. It's a reflection of the language's limited, closed, imperfect, and poorly extensible design, which makes it look like the work of a novice - regardless of the authority of its designers and implementers. Extended reading: the Go language is a 30-year-old antiquated design idea, and user-defined things are almost always second-class citizens (Tikhon Jelvis).
At the end of the day, this one is caused by incomplete support for generalizations.
Go doesn't have a lot of great features, but the combination of Go's features and tools just works.
This is where Go is great.
1.11 Without generic support, interfaces to common datatypes are ugly
Without generic support, interfaces to common basic datatypes such as List, Set, and Tree can only be ugly: you put in a concrete type, and then you take it out as an untyped interface{} (which can be considered the base type of all types), and you have to force a type conversion to get it back. Go lacks functions such as min and max, the absolute value function abs only receives/returns double-precision decimals, and the sorting interface can only avoid the type of the object being compared with the help of sort.Interface, and so on and so forth, all of which are the result of the lack of generalization. Go developers did not explicitly reject generalization, they just said that they had not found a good way to implement it (can you learn a language that has been open-sourced?). The reality is that Go 1.0 has been finalized, and generalization hasn't, and those ugly interfaces have to be around for a long time in order to maintain backward compatibility.
Go has its own philosophy, and they wouldn't object to a generic implementation that didn't conflict with their current philosophy.
If it's simply a matter of learning (or copying) the syntax of a language that's already open-sourced, that's C++'s design style (or rather, that's the way it's always been designed, to copy whatever features are available), which has led to all sorts of brain-dead programming styles.
Compile-time generalizations and run-time generalizations may not be completely compatible, see this example:
type Adder<T> interface {
Add(a, b T) T
}