Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Go is still young as programming languages evolve. It was first released on November 10, 2009. Its creators, Robert Griesemer Rob Pike and Ken Thompson, work at Google, where the challenge of scaling up at scale inspired them to design Go as a fast and efficient programming solution for managing multiple developers with large code bases, Has stringent performance requirements and spans multiple networks and processing cores. Go’s founders also took the opportunity to learn the strengths, weaknesses, and vulnerabilities of other programming languages when they created their new language. The result is a clean, clear, and functional language with a relatively small set of commands and features.

In this article, today’s article will introduce you to 9 features that make Go different from other languages.

1. Go always includes binaries 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 programs to work properly.

Including the runtime directly in the binary makes it very easy to distribute and run the Go program, and avoids incompatibilities between the runtime and the program. Virtual machines for languages like Python, Ruby, and JavaScript are also not optimized for garbage collection and memory allocation, which explains Go’s superior speed relative to other similar languages. For example, Go stores as much as possible on a stack, where the data is arranged in order for faster access than the heap. More on that later.

The last thing about Go’s static binaries is that they start very quickly because there are no external dependencies to run. This is useful if you use a service like Google App Engine, a platform-as-a-service running on the Google Cloud that reduces your application to zero instances to save on Cloud costs. When a new request is received, App Engine can launch an instance of the Go program in the blink of an eye. The same experience in Python or Node typically results in a 3-5 second (or longer) wait because the required virtual environment also starts with the new instance.

2. Go has no centralized hosting service 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 through their source code repositories, most commonly Github. Libraries can be downloaded in this way on the Go Install command line. Why do I like this feature? I’ve always thought centrally managed dependency services like Maven Central, PIP, and NPM are a bit of a daunting black box that may abstract away the hassle of downloading and installing dependencies, but inevitably causes the dreaded cardiac arrest to occur when a dependency error occurs.

In addition, making your modules available to others is as easy as putting them into version control, which is a very simple way to distribute programs.

3. Go is called by value

In Go, when you supply a primitive value (a number, a Boolean, or a string) or a structure (a rough equivalent of a class object) as an argument to a function, 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 receiver function are reflected in the original object.

In Go, structs and primitives are passed by value by default, and Pointers can optionally be passed by using the asterisk operator:

// Pass by value
func MakeNewFoo(f Foo ) (Foo, error) { 
   f.Field1 = "New val" 
   f.Field2 = f.Field2 + 1 
   return f, nil 
}
Copy the code

The above function takes a copy of Foo and returns a new Foo object.

// Pass by reference
func MutateFoo(f *Foo ) error { 
   f.Field1 = "New val" 
   f.Field2 = 2 
   return nil 
}
Copy the code

The above function takes a pointer to Foo and changes the original object.

This stark difference between calling by value and calling by reference makes your intentions clear and reduces the likelihood that the calling function will inadvertently change the object passed in (hold tight when it shouldn’t happen (which many beginner developers have a hard time doing).

As MIT sums it up: “Variability makes it harder to understand what your program is doing and harder to enforce contracts.”

In addition, calling by value significantly reduces the work of the garbage collector, which means faster and more memory efficient applications. This article concludes that pointer tracing (retrieving pointer values from the heap) is 10 to 20 times slower than retrieving values from a continuous stack. A good rule of thumb to keep in mind is that 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, I manually manage database connections in my code by creating a database pool before I start using Knex.js, and then open a new connection from the pool in each function, once the required database CRUD functionality is complete.

This is a bit of a maintenance nightmare, because if I don’t release connections at the end of each function, the number of unreleased database connections slowly grows until there are no more connections available in the pool, and then the application is interrupted.

The reality is that programs often need to free, clean up, and dismantle resources, files, connections, and so on, so Go introduces the defer keyword as an effective way to manage these.

Any statement that begins with defer will defer calling it until the surrounding functions exit. This means that you can put the clean/dismantle code at the top of the function (obviously), knowing that it will once the function is finished.

func main(a) {                        
    if len(os.Args) < 2 {   
        log.Fatal("no file specified")
    }  
    f, err := os.Open(os.Args[1])                        
    iferr ! =nil {                         
        log.Fatal(err)                        
    }                        
    defer f.Close()                        
    data := make([]byte.2048)                        
    for {                         
        count, err := f.Read(data)                                               
        os.Stdout.Write(data[:count])                        
        iferr ! =nil {                          
            iferr ! = io.EOF { log.Fatal(err) }break}}}Copy the code

In the example above, the file closure method is delayed. I like the pattern of declaring your housekeeping intent at the top of a function and then forgetting about it, 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 embraces its best features. In the Go:

  • Functions are values, which means they can be added as values to maps, passed as parameters to other functions, set as variables, and returned from functions (called “higher-order functions,” decorators are often used in Go to create middleware patterns).
  • Anonymous functions can be created and automatically called.
  • Functions declared inside other functions allow closures (functions declared inside functions can access and modify variables declared outside functions). In the idiomatic Go, closures are widely used to limit the scope of a function and set the state that the function then uses in its logic.
func StartTimer (name string) func(a){
    t := time.Now()
    log.Println(name, "started")
    return func(a) {
        d := time.Now().Sub(t)
        log.Println(name, "took", d)
    }
}
func RunTimer(a) {
    stop := StartTimer("My timer")
    defer stop()
    time.Sleep(1 * time.Second)
}
Copy the code

Here is an example closure. The ‘StartTimer’ function returns a new function that, through a closure, can access the value of ‘t’ set in its birth range. This function can then compare the current time to the value of “t” to create a useful timer. Thanks to Mat Ryer for this example.

6. Go has implicit interfaces

Anyone who has read the literature on SOLID coding and design patterns has probably heard the “combination-first over inheritance” mantra. In short, this suggests that you should decompose business logic into different interfaces, rather than relying on hierarchical inheritance of attributes and logic from parent classes.

Another popular approach is to “program the interface, not implement it” : aN API should only publish a contract for its expected behavior (its method signature), not details about how to implement that behavior.

Both point to the importance of interfaces in modern programming.

So it’s 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 explicitly implemented, but implicitly implemented. A concrete type does not declare that it implements an interface. Conversely, if the set of methods set for that concrete type contains all the method sets of the underlying interface, Go considers the object to implement the interface.

This implicit interface implementation (formally known as structural typing) allows Go to enforce type-safety and decoupling, preserving much of the flexibility shown in dynamic languages.

By contrast, explicit interfaces bind the client and implementation together, making it much harder to replace dependencies in Java than in Go, for example.

// This is an interface declaration (called Logic)
type Logic interface { 
    Process (data string) string 
}

type LogicProvider struct {}
// This is the LogicProvider method struct named "Process"
 func (lp LogicProvider) Process (data string) string { 
    // Business logic
}
// This is a client structure with Logic interfaces as attributes
type Client struct { 
    L Logic 
}
func(c Client) Program(a) { 
    // Get data from somewhere
    cLProcess(data) 
}
func main(a) { 
    c := Client { 
        L: LogicProvider{},  
     } 
    c.Program() 
}
Copy the code

There is no declaration in LogicProvider that it complies with the Logic interface. This means that a client can easily replace its logical provider in the future, as long as the logical provider contains all the method sets of the underlying interface (Logic).

7. Error handling

Errors are handled very differently in Go than in other languages. In short, Go handles errors by returning a value of type error as the last return value of the function.

The error argument returns nil when the function executes as expected, otherwise the error value is returned. Call the function and then check the error return value and handle the error, or throw your own error.

The function returns an integer and an error
func calculateRemainder(numerator int, denominator int) ( int, error ) { 
   //
   if denominator == 0 { 
      return 9, errors.New("denominator is 0"
    }
   // No error is returned
   return numerator / denominator, nil
 }
Copy the code

Go works this way for a reason: It forces the coder to consider exceptions and handle them correctly. Traditional try-catch exceptions also add at least one new code path to the code and indent it in hard-to-follow ways. Go prefers to treat the happy path as non-indented code, identifying and returning any errors before the happy path is complete.

8. Concurrent

Arguably Go’s most famous feature, concurrency allows processing to run in parallel on the number of available cores on the machine or server. Concurrency makes the most sense when individual processes are not dependent on each other (and do not need to run sequentially) and when time performance is critical. This is usually the case with I/O requirements, where reading or writing to disk or network is orders of magnitude slower than all but the most complex in-memory processes. The “go” keyword before the function call will run the function simultaneously.

func process(val int) int { 
   // Do something with val
}
// For each value in' in', run the process function simultaneously,
// Read the result of process into 'out'
 func runConcurrently(in <-chan int, out chan<- int){ 
   go func(a) { 
       for val := range in { 
            result := process(val) 
            out <- result   
       } 
   } 
}
Copy the code

Concurrency in Go is an in-depth and fairly advanced feature, but where it makes sense, it provides an effective way to ensure optimal program performance.

Go standard library

Go has a “battery inclusive” philosophy, and many of the requirements of modern programming languages are incorporated into the standard library, making life easier for programmers. As mentioned earlier, Go is a relatively young language, which means that many of the problems/requirements of modern applications can be met in the standard library.

On the one hand, Go provides world class support for networking (HTTP/2 in particular) 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 otherwise) is very simple, which explains the popularity of Go in REST-based HTTP Web service development.

As Mat Ryer also points out, the standard library is open source and an excellent way to learn Go best practices.


🛬 wuhu! Take off!

I’ve been writing tech blogs for a long time, mostly through Nuggets, and this is my tutorial on nine features that make Go different from other languages. I like to share technology and happiness through articles. You can visit my blog at juejin.cn/user/204034… For more information. Hope you like it! 😊

If you really learn something new from this article, like it, bookmark it and share it with your friends. 🤗 and finally, don’t forget ❤ or 📑