Original link: https://blog.henix.info/blog/why-choose-golang/
You may not believe it, but when I was working on a personal project recently, I gave up Ruby and F# and chose the Go language. Below I will explain the reasons for this.
origin
First of all, I really need a language with GC to do some fast prototyping development. My expectation for this language is that it can be developed quickly and has a certain expressive power.
Previously my language of choice for rapid development was Ruby. But lately I’ve been finding more and more problems with Ruby:
- The community is becoming less and less active, it seems that people are slowly leaving Ruby, many gems are not well maintained
- As a dynamically typed language, the long-term maintainability of the written code is not very good
So I hope to find a static language. I chose F# at first. because:
- Functional, with a powerful type system, very expressive
- Backed by the .net community, no matter how bad it is, Microsoft will support it, and the standard library will not be worse
When I actually started writing programs, I checked the .net documentation, especially the HttpListener that implements the http server. I found that the problem with F# is: the standard library shares a set with C#, and the standard library of C# basically learns Java and is full of OOP design. They did not take advantage of the functional features of F# to design a set of standard libraries covering at least IO, HTTP, and JSON specifically for F#. So I started switching to Go.
What matters is the standard library, asshole!
If I write F#, all I have to live with is this standard library full of object-oriented legacy. And if I write Go, what I have to endure is this poor syntax.
Whichever is the lesser evil, I’d rather put up with Go’s syntax (and Go 1.18 supports generics). Go’s syntax is crude but adequate.
For me, the plus point of Go is that its standard library adopts the design of composalbe interfaces, which I think is very beautiful. When I write C code myself, I often refer to the design of the Go standard library.
Why Object Orientation is Toxic (or: Why I Prefer Fat Pointers)
It is impossible for any practical program to be without abstraction. There are generally two abstraction methods for polymorphism:
- Object-oriented, inheritance, encapsulation. A class must declare which interfaces it implements. The implementation uses a virtual table (vtable). Representative languages are C++, Java, Python, C#
- Composable interface. A class does not need to declare which interfaces it implements. The code for a class to implement an interface can be separate from the code for the class. Implementation using fat pointer (fat pointer). Representative languages include Rust, Go, and Haskell (I don’t know if type classes count)
There is also a relatively special C language, which can support both paradigms, but it must be simulated and implemented by itself.
If your program requires dynamic dispatch, you must pay some price for abstraction. The point is, which part of the code pays for this price?
In object-oriented languages, the price is paid by the class definition that implements the interface. Its form is a virtual table. For example, if each object in C++ inherits a base class with a virtual method, there will be an extra pointer to the virtual table at the beginning of the object.
In a language (paradigm) of composable interfaces, the cost is paid by the user of the interface. The user needs two pointers, one pointing to the object and one pointing to the vtable.
For example, many times we will encounter a task: how to convert any object into a string?
Suppose we define an interface Stringable that can be converted to String:
interface Stringable { string toString(); }
In a traditional object-oriented programming language, every class that we want to convert to a string, such as Integer, Date, etc., needs to implement this interface. So we might as well make a super base class and define a toString method on this super base class (think Java’s Object.toString).
But the problem is that this design is not scalable. If JSON, BSON, or some other serialization format appears in the future, does each format need to add a method to this base class? Many times the base class is defined in the base library, and it is impossible for us to modify its definition.
Another solution to this problem is, don’t toss about the definition side, let’s toss the user side.
For example, if I define a Date class, there is no toString on it:
class Date { int year, month, day; }
But where the Date is used, two pointers are passed in: one is the Date object itself, and the other is Date_toString, which is the function (pointer) that converts this object into a String.
void printDate(Date d, func dateToString);
The dateToString here is a function, its use method is to pass in a Date object and return a String.
In this way, the object and the interface are separated. If you need toJSON and toBSON in the future, you don’t need to modify the source code of the original Date object, and you can put these codes in a new module.
The core idea of object-oriented is: data and related operations should be bound together. But from this example, we can see that in many cases, data and operations should be separated, and forcibly binding them together will increase unnecessary coupling. From my own programming practice, for some high-level modules, the separation of data and operations can make the code easier to reuse.
Some people may say: You can also write programs like this in Java. But Java’s entire standard library is designed around object-oriented, and it’s hard to get back. The Go language has no historical burden, and the standard library is completely designed around composable interfaces, so I think Go’s standard library is very worth learning.
Why I don’t think Go’s error handling is hard to use
Another complaint about Go that is often seen on the Internet is that error handling is not easy to use. I think Go’s error handling is actually similar to exception handling in terms of user experience.
And in the process of using Go, I have some new understanding of error handling.
We can divide errors into 3 categories:
-
User input errors (including URL parameters, configuration files, etc.), such errors need to be returned and displayed to the user. It is best not to use the built-in error type of the programming language to represent this kind of error, but to use a custom type, such as a struct, or the simplest string, or use a null value in the type to represent the error, such as “” , -1, nil etc.
-
Program error. Can be further divided into 2 categories:
- Unexpected, unrecoverable error. The best way to deal with this kind of error is fail-fast, print a call stack and exit
- An error that is expected by the programmer and can be recovered or retried. Like a network error. This type of error is represented by the language’s built-in mechanism (such as Go’s error or other language’s exception), which can be returned, passed, and saved between functions
Have you seen it, I think only the last kind of error is suitable for using error, and other errors either use custom types or directly fail-fast. If you approach errors this way, I don’t think Go’s ” error-as-value ” approach is that hard to use.
What you have to do is to use the following code snippet more to fail-fast:
func Ok(err error) { if (err != nil) { panic(err) } }
Summarize
Choosing a language for a personal project is a very personal thing, what matters is what you can learn from using the language. The main thing I learned from the Go language was how to design the standard library with composable interfaces, which helped me a lot, and that was enough.
PS For the same reason, my ” heavy language ” for personal projects also switched from C++ to C.
PS2 Now even some of my bash scripts are written in Go. The advantage of writing such operation and maintenance scripts in Go is that it can easily take advantage of multi-core parallelism.
Related Links
- Which design is better between Golang and Rust’s fat pointer and C++’s pointer to virtual table?
- Why both go and rust languages abandon inheritance?
- Were fat pointers a good idea?
related articles
This article is transferred from: https://blog.henix.info/blog/why-choose-golang/
This site is only for collection, and the copyright belongs to the original author.