With the development of programming languages, Go is still very young. It was first released on November 10, 2009. Its creator Robert Griesemer
Rob Pike and Ken Thompson worked at Google, where the challenge of scaling at scale inspired them to design Go as a fast and efficient programming solution for having large code bases, managing by multiple developers, with stringent performance requirements, and spanning multiple networks and processing cores.
The founders of Go also took the opportunity to learn the advantages, disadvantages, and vulnerabilities of other programming languages when they created their new language. The result is a clean, clear and practical language with relatively few commands and feature sets.
In this article, today's article will introduce you to 9 features of Go different from other languages.
1. Go always contains binary files in the build
The Go runtime provides services such as memory allocation, garbage collection, concurrency support and networking. It is compiled into each Go binary. This is different from many other languages, many of which use virtual machines that need to be installed with the program to work properly.
Including runtime directly in binary files makes it very easy to distribute and run Go programs and avoids incompatibility between runtime and program. Virtual machines in languages such as Python, Ruby, and JavaScript are also not optimized for garbage collection and memory allocation, which explains Go's superior speed over other similar languages. For example, Go will store as much as possible on the stack, where the data is arranged in order for faster access than the heap. We will introduce it in detail later.
The last thing about Go's static binary is that they start very fast because they don't need to run external dependencies. It's useful if you use a service like Google App Engine, a platform-as-a-service running on Google Cloud that can reduce your app to zero instances to save cloud costs. When a new request is received, App Engine can start an instance of the Go program in the blink of an eye. The same experience in Python or Node usually results in 3-5 seconds (or more) of waiting, as the required virtual environment will also start with the new instance.
2. Go does not have centralized hosting services for program dependencies
To access published Go programs, developers do not rely on centrally managed services, such as Java's Maven Central or JavaScript's NPM registry. Instead, projects are shared via their source code repository (most commonly Github). The go install command line allows the library to be downloaded in this way.
Why do I like this feature? I've always thought centrally managed dependency services like Maven Central, PIP, and NPM were a bit daunting black box that might abstract the hassle of downloading and installing dependencies, but inevitably triggering a terrible heartbeat when the dependency is wrong.
In addition, providing the module to others is as easy as putting it into a version control system, which is a very simple way to distribute programs.
3. Go is called by value
In Go, when you provide a primitive value (number, boolean, or string) or a structure (a rough equivalent of a class object) as a function's argument, Go always copies the value of the variable.
In many other languages, such as Java, Python, and JavaScript, primitives are passed by value, but objects (class instances) are passed by reference, meaning that the receiving function actually receives a pointer to the original object, not a copy of it. Any changes made to the object in the receive function will be reflected in the original object.
In Go, structures and primitives are passed by value by default, and you can choose to pass pointers by using the asterisk operator:
// Pass by valuefunc MakeNewFoo(f Foo ) (Foo, error) { f.Field1 = "New val" f.Field2 = f.Field2 + 1 return f, nil }
The above function takes a copy of Foo and returns a new Foo object.
// Pass by referencefunc MutateFoo(f *Foo ) error { f.Field1 = "New val" f.Field2 = 2 return nil }
The function above takes a pointer to Foo and changes the original object.
This obvious difference between calling by value vs. calling by reference makes your intention obvious and reduces the possibility of calling a function inadvertently changing the incoming object (when it shouldn't happen (it's hard for many beginner developers to do this).
As MIT summarizes: “Variability makes it harder to understand what your program is doing and harder to execute a contract”
Additionally, calling by value significantly reduces the work of the garbage collector, which means faster applications and more memory efficient. This article concluded that pointer tracking (retrieving pointer values from the heap) is 10 to 20 times slower than searching values from a continuous stack. A good rule of thumb to remember is: the fastest way to read from memory is to read sequentially, which means minimizing the number of pointers stored randomly in RAM.
4. ‘defer’ keyword
In NodeJS, before I start using it, I will manually manage the database connections in my code by creating a database pool, and then open a new connection from the pool in each function once the required database CRUD functionality is complete.
It's kind of like a maintenance nightmare, because if I don't release the connection at the end of each function, the number of unreleased database connections slowly grows until there are no more available connections in the pool and then break the application.
The reality is that programs often need to release, clean up and tear down resources, files, connections, etc., so Go introduced the defer keyword as an effective way to manage these.
Any statement starting with defer will delay the call to it until the surrounding functions exit. This means you can put your cleanup/disassembly code at the top of the function (obviously), knowing that it will be the case once the function is finished.
func main() { if len() < 2 { ("no file specified") } f, err := ([1]) if err != nil { (err) } defer () data := make([]byte, 2048) for { count, err := (data) (data[:count]) if err != nil { if err != { (err) } break } } }
In the above example, the file closing method was delayed. I like this pattern of declaring your housekeeping intentions at the top of the function and then forgetting it and knowing that once the function exits it will do its job.
5. Go adopts the best features of functional programming
Functional programming is an efficient and creative paradigm, and fortunately Go adopts the best features of functional programming. In Go:
- Functions are values, which means they can be added as values to the map, passed as parameters to other functions, set as variables, and returned from the function (called "higher-order functions", which are often used in Go to create middleware patterns using decorators).
- Anonymous functions can be created and automatically called.
- Functions declared within other functions allow closures (functions declared within functions can access and modify variables declared in external functions). In idiomatic Go, closures are widely used to limit the scope of a function and set the state that the function is then used in its logic.
func StartTimer (name string) func(){ t := () (name, "started") return func() { d := ().Sub(t) (name, "took", d) } } func RunTimer() { stop := StartTimer("My timer") defer stop() (1 * ) }
Above is an example of a closure. The 'StartTimer' function returns a new function that allows access to the 't' value set within its birth range through a closure. This function can then compare the current time to the value of "t", thus creating a useful timer. Thanks to Mat Ryer for this example.
6. Go has an implicit interface
Anyone who has read the literature on SOLID coding and design patterns may have heard of the mantra of “priority combination over inheritance”. In short, this suggests that you should break down your business logic into different interfaces, rather than relying on hierarchical inheritance of properties and logic from the parent class.
Another popular approach is to "program the interface, not implement it": APIs should publish only contracts for their intended behavior (its method signatures) rather than detailed information on how to implement it.
Both of these indicate the importance of interfaces in modern programming.
Therefore, it is no surprise that Go supports interfaces. In fact, interfaces are the only abstract type in Go.
However, unlike other languages, interfaces in Go are not implemented explicitly, but implicitly. The specific type does not declare that it implements the interface. Instead, if the method set for this concrete type contains all method sets of the underlying interface, Go believes that the object implements the interface.
This implicit interface implementation (formally known as the structural type) allows Go to enforce type safety and decoupling, maintaining most of the flexibility shown in dynamic languages.
In contrast, explicit interfaces bind clients and implementations together, for example, replacing dependencies in Java is much more difficult than in Go.
// This is an interface declaration (called Logic)type Logic interface { Process (data string) string } type LogicProvider struct {} // This is the method called "Process" on LogicProvider func (lp LogicProvider) Process (data string) string { // Business logic} // This is a client structure with the Logic interface as a propertytype Client struct { L Logic } func(c Client) Program() { // Get data from somewhere cLProcess(data) } func main() { c := Client { L: LogicProvider{}, } () }
There is no declaration in LogicProvider that it complies with the Logic interface. This means that clients can easily replace their logic provider in the future, as long as that logic provider contains all method sets of the underlying interface (Logic).
7. Error handling
Error handling in Go is very different from other languages. In short, Go handles errors by returning a value of type error as the last return value of the function.
When the function executes as expected, the error parameter returns nil, otherwise the error value is returned. Call the function and check the error return value, and handle the error, or throw your own error.
// The function returns an integer and an errorfunc calculateRemainder(numerator int, denominator int) ( int, error ) { // if denominator == 0 { return 9, ("denominator is 0" } // No error returns return numerator / denominator, nil }
There is a reason Go runs this way: It forces coders to consider exceptions and handle them correctly. The traditional try-catch exception also adds at least one new code path to the code and indents the code in a difficult way. Go prefers to treat "happy path" as a non-indent code, identifying and returning any errors before "happy path" is completed.
8. Concurrency
Arguably the most famous feature of Go, concurrency allows processing to run in parallel on the number of available cores on the machine or server. Concurrency makes the most sense when separate processes are not interdependent (no need to run sequentially) and time performance is critical. This is usually the case where I/O requirements are required, where reading or writing to disk or network is orders of magnitude slower than all processes except the most complex in-memory processes.
The "go" keyword before the function call will run the function at the same time.
func process(val int) int { // Do something with val} // For each value in 'in', run the process function at the same time,// and read the result of process to 'out' func runConcurrently(in <-chan int, out chan<- int){ go func() { for val := range in { result := process(val) out <- result } } }
Concurrency in Go is an in-depth and fairly advanced feature, but where it makes sense, it provides an effective way to ensure optimal performance of your program.
9. Go Standard Library
Go has a "battery included" concept, and many requirements of modern programming languages are incorporated into the standard library, which makes programmers' lives easier.
As mentioned earlier, Go is a relatively young language, which means that many issues/requirements of modern applications can be met in the standard library.
On the one hand, Go provides world-class support for networking (especially HTTP/2) and file management. It also provides native JSON encoding and decoding. Therefore, setting up a server to handle HTTP requests and return a response (JSON or something) is very simple, which explains the popularity of Go in the development of REST-based HTTP Web Services.
As Mat Ryer also points out, the standard library is open source and is an excellent way to learn Go best practices.
If you really learn something new from this post, like it, bookmark it and share it with your friends. 🤗Lastly, don't forget ❤ or 📑 to support it
This is the end of this article about nine different features of Golang from other languages. For more related Golang feature content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!