Hello everyone,
I'm a Node.js developer with over three years of experience who recently made the transition to Golang for Backend development. Over the past six months, I've worked on three different projects using the Fiber framework, which I find to be the latest and most impressive Backend framework I've encountered so far and I will tell about it in another blog.
Today, I will tell you about the difficult and joyful journal that I experienced last time with more crying and smiling cause of the magic of this language. Below is my 3 favorite GIST about Golang I noted and hope can be useful for you.
1. Traverse an array and update elements
This is painful for me as a JS man and I made this mistake many times in the early time tangent with Golang. It's like the old saying goes, 'You can't step into the same river twice,' yet here I am, repeating my missteps.
Like C/C++, Golang impressed me with the pointer, that I had forgotten since leaving my university. Now let's deep dive into this example code with me.
Problem
This is a common mistake for developers coming from languages like JavaScript where the concept of pointers is abstracted away. In Go, understanding pointers is crucial for efficient manipulation of data structures, especially when dealing with slices.
// Wrong code for _, addr := range addresses { addr.Token = TokenOutput{ ID: token.ID, Currency: token.Currency, NetworkID: token.NetworkID, Address: token.Address, Decimals: token.Decimals, } addr.PoolAddr = poolAddr.Address } // Right code for idx, _ := range addresses { addresses[idx].Token = TokenOutput{ ID: token.ID, Currency: token.Currency, NetworkID: token.NetworkID, Address: token.Address, Decimals: token.Decimals, } addresses[idx].PoolAddr = poolAddr.Address }
Explanation
In the "Wrong code" snippet, the loop variable
addr
is a copy of the element from the addresses
slice. Modifying addr
doesn't affect the original slice element. So when you set addr.Token
and addr.PoolAddr
, you're modifying the copy, not the actual elements in the addresses
slice.The "Right code" snippet solves this issue by directly accessing the elements in the
addresses
slice using the index idx
. By doing addresses[idx].Token
and addresses[idx].PoolAddr
, you're modifying the elements in the slice directly. This ensures that the changes made inside the loop persist in the original slice.Summary
The key difference lies in modifying the elements of the slice directly by accessing them via the index (
addresses[idx]
) rather than using a copy of the element (addr
). Understanding this difference is essential for correct manipulation of slices in Go.2. JSON formatted
// Log all headers headers := make(map[string]string) c.Request().Header.VisitAll(func(k, v []byte) { headers[string(k)] = string(v) }) headersBuffer, _ := json.Marshal(headers) log.Print("headers: ", string(headersBuffer))
Explanation:
Here, we're iterating over all the headers of an HTTP request using the
VisitAll
method. For each header, we're converting the key and value to strings and storing them in a map. After collecting all headers, we marshal the map into JSON format using json.Marshal
and then log it. This allows for easy inspection of request headers in a structured format.3. Race condition in channel processing
func GetListTransactionV1(networkName string, networkType string, addresses []string) []string { txList := make(chan []string) semaphore := make(chan struct{}, 2) // Limit to 2 concurrent goroutines var wg sync.WaitGroup for _, address := range addresses { wg.Add(1) go func(addr string) { defer wg.Done() //Add semaphore to limit the number of concurrent goroutines with 2 times semaphore <- struct{}{} // Acquire semaphore defer func() { <-semaphore }() // Release semaphore when done // Call 3rd-party API with rate-limit txs, err := walletOS.GetTransactionsList(networkName, networkType, addr) if err != nil { log.Printf("Error fetching transactions for address %s: %s", addr, err) return } network := walletOS.GetPslNetwork(walletOS.BlockchainType(networkName), walletOS.Network(networkType)) _txList := []string{} for _, tx := range txs.Transactions { txHash := walletOS.ExtractTxnEntity(network, tx) _txList = append(_txList, txHash) } txList <- _txList }(address) } go func() { wg.Wait() close(txList) }() result := []string{} for txs := range txList { result = append(result, txs...) } return result }
Explanation:
In this code, we're iterating over a list of addresses and making concurrent API calls to fetch transactions for each address. To limit the number of concurrent API calls and avoid race conditions, we're using a semaphore with a capacity of 2 (
semaphore := make(chan struct{}, 2)
). Each goroutine acquires a semaphore before making the API call and releases it when done.This ensures that at most two goroutines are running concurrently. Once all API calls are finished, we close the
txList
channel to signal that no more data will be sent. We then collect all the results from the channel and return them.Using semaphores helps prevent race conditions and ensures that API calls are made concurrently within a safe limit.