[Golang in real combat] - My favorite gist

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.
 

Loading Comments...

Follow me @kevinbkdev

Donate to me
Bank QR

Bank QR Code

Buy me a coffee
Buy Me A Coffee

@source-blog by @thanhledev

Thứ 7 (24-12) lúc 9 giờ sáng mình có buổi workshop nhỏ chia sẻ cách viết Smart Contract dùng Solidity, target là chỉ cần biết code là làm được.
Nếu bạn hứng thú hãy tham gia nhé!