I have always been interested in learning new programming languages, especially those that offer advantages over the ones I already know. That’s why I decided to learn GO, a language created by Google and designed for concurrency, simplicity and performance.
Here is a summary of what I have learned so far.
A quick overview of GO
GO is a compiled, statically typed language with a syntax similar to C but with features that make it more expressive and readable, such as garbage collection, goroutines, channels, interfaces and defer statements. GO also has a rich standard library that covers a wide range of domains, such as networking, cryptography, compression, testing and web development.
I wrote a simple GO program, which asks for the user’s age and then calculates their age in seconds. It should give you a basic understanding of the syntax:
package main
import (
"fmt"
"bufio"
"os"
"strconv"
"time"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter your age: ")
text, _ := reader.ReadString('\n')
age, err := strconv.Atoi(text[:len(text)-1])
if err != nil {
fmt.Println("Invalid input. Please enter a number.")
return
}
seconds := int64(age) * 365 * 24 * 60 * 60
fmt.Printf("You are approximately %d seconds old.\n", seconds)
}
This program reads the user’s age from the standard input, converts it to an integer, and then calculates the number of seconds based on the approximation that there are 365 days in a year, 24 hours in a day, 60 minutes in an hour, and 60 seconds in a minute. The result is then printed to the standard output.
Why I chose GO
One of the reasons I chose GO over other languages is that it is speedy and efficient. Thanks to the lightweight goroutines that can communicate through channels, GO programs can run on multiple cores and processors without much overhead. GO also has a powerful toolchain that makes it easy to compile, test, format and debug code. GO also supports cross-compilation, meaning I can create binaries for different platforms from a single source code.
To demonstrate the benefits of running code on multiple cores and processors in GO, a simple program performs CPU-intensive calculations concurrently using Goroutines. In the following code snippet, we calculate the factorial of a set of numbers concurrently:
package main
import (
"fmt"
"sync"
"time"
)
// factorial calculates factorial of n and sends the result to channel c
func factorial(n int, c chan int) {
result := 1
for i := 2; i <= n; i++ {
result *= i
}
c <- result
}
func main() {
// Input slice of numbers whose factorial needs to be calculated
nums := []int{20, 25, 30, 35, 40, 45, 50}
// Create a channel to receive factorial results
c := make(chan int, len(nums))
// Create a WaitGroup to wait for all Goroutines to finish
var wg sync.WaitGroup
startTime := time.Now()
// Launch a Goroutine for each number
for _, num := range nums {
wg.Add(1)
go func(n int) {
defer wg.Done()
factorial(n, c)
}(num)
}
// Wait for all Goroutines to finish
go func() {
wg.Wait()
close(c)
}()
// Print the received results
for result := range c {
fmt.Println("Received:", result)
}
elapsedTime := time.Since(startTime)
fmt.Printf("Time taken: %s\n", elapsedTime)
}
In this example, each call to factorial
runs in its own Goroutine, allowing it to run concurrently with other Goroutines. This takes advantage of multi-core processors by distributing the work among available CPU cores, improving performance.
Another reason I chose GO is that it is straightforward and elegant. GO has a small and consistent set of keywords and rules, which makes it easy to learn and write. GO also enforces good coding practices, such as formatting, documentation and error handling. GO also has a built-in testing framework that encourages writing tests alongside the code. GO also has a feature called “go fmt” that automatically formats the code according to a standard style guide, which makes the code more readable and consistent.
Below is a simple GO program that demonstrates reading user input, calculating the factorial of a number, and showcasing basic error handling. It uses a small set of keywords like import
, func
, var
, if
, else
, etc., which are fundamental to GO:
package main
import (
"fmt"
"strconv"
)
// Calculate factorial using recursion
func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}
func main() {
var input string
// User input
fmt.Print("Enter an integer to calculate its factorial: ")
fmt.Scanln(&input)
// Convert string to integer
n, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer.")
return
}
// Compute and display the factorial
result := factorial(n)
fmt.Printf("The factorial of %d is %d\n", n, result)
}
Here’s a breakdown of what’s happening in the code:
- Import Packages:
fmt
for formatting and input/output,strconv
for converting string to integer. - Factorial Function: A straightforward recursive function that calculates the factorial of a number.
- Main Function: This is where the program execution starts.
- It reads an integer input from the user.
- Converts the input string to an integer.
- Checks for errors (e.g., if the user entered a non-integer value).
- Calls the
factorial
function and prints the result.
Challenges
One of the challenges I faced while learning GO was getting used to its error handling mechanism. Unlike some other languages that use exceptions or return values, GO uses a special value called “error” that represents any possible error that can occur during the execution of a function. This means that I have to check for errors after every function call and handle them accordingly. This can sometimes make the code verbose and repetitive, but it also forces me to think about all the possible scenarios and handle them gracefully.
Another challenge I faced while learning GO was understanding its concurrency model. Although goroutines and channels are very powerful and elegant concepts, they can also be tricky to use correctly. I had to learn how to avoid common pitfalls, such as deadlock, race conditions and memory leaks. I also had to learn how to use some tools and techniques, such as sync package, mutexes, wait groups and select statements, to coordinate and synchronize multiple goroutines.
Thoughts So Far
Despite these challenges, I am enjoying learning GO, and it is a great language for developing modern applications. I have used GO to create some basic web applications and command-line tools and now experimenting with microservices. I have also learned a lot about concurrency, performance and simplicity along the way.
I will share more of my journey learning GO over time—lots to learn. Stay tuned.