Useful and Versatile Go Code Snippets
During the development of production projects, I often find myself repeating code and unconsciously using certain techniques until I realize them later during a retrospective.
To address this issue, I developed a solution that has been very helpful for me, and I believe it might be useful for others as well.
Here are some useful and versatile code snippets randomly selected from my utility library, without specific categorization or system-specific techniques.
# 1. Timing Execution
If you want to track the execution time of a function in Go, there’s a simple and efficient technique with just one line of code, using the defer
keyword. You only need a TrackTime
function:
1 | // Utility |
# 1.5. Two-Stage Deferred Execution
Go’s defer
is not only for cleanup tasks but can also be used for preparation tasks. Consider the following example:
1 | func setupTeardown() func() { |
The beauty of this pattern is that with just one line of code, you can accomplish tasks like:
- Opening a database connection and then closing it.
- Setting up a mock environment and then tearing it down.
- Acquiring a distributed lock and then releasing it.
“Well, that seems clever, but how practical is it in real-world scenarios?”
Remember the timing execution technique? We can do something like this:
1 | func TrackTime() func() { |
Note! What if there’s an error when I connect to the database?
Indeed, patterns like defer TrackTime()
or defer ConnectDB()
might not handle errors properly. This technique is most suitable for testing or when you are willing to take the risk of fatal errors, as shown in the following test-oriented approach:
1 | func TestSomething(t *testing.T) { |
This way, database connection errors can be handled during testing.
# 2. Pre-allocate Slices
According to insights from the article “Performance Improvements in Go,” pre-allocating slices or maps can significantly improve the performance of a Go program. However, it’s worth noting that if we are not careful and use append
instead of indexing (like a[i]
), this approach may sometimes lead to errors. Did you know that we can use a pre-allocated slice without specifying the array length (zero) as explained in the mentioned article? This allows us to use the pre-allocated slice just like using append
:
1 | // Instead of |
# 3. Method Chaining
Method chaining technique can be applied to function (pointer) receivers. To illustrate this, let’s consider a Person
struct with two functions AddAge
and Rename
for modifications:
1 | type Person struct { |
If you want to add age to a person and then rename them, the conventional way is:
1 | func main() { |
Alternatively, we can modify the AddAge
and Rename
functions’ receivers to return the modified object itself, even though they typically don’t return anything:
1 | func (p *Person) AddAge() *Person { |
By returning the modified object itself, we can easily chain multiple function receivers together without adding unnecessary lines of code:
1 | p = p.AddAge().Rename("Aiden 2") |
# 4. Go 1.20 Allows Slicing into an Array or Array Pointer
When we need to convert a slice to a fixed-size array, direct assignment is not possible, for example:
1 | a := []int{0, 1, 2, 3, 4, 5} |
With the introduction of this feature in Go 1.17, and with the release of Go 1.20, the conversion process becomes simpler with more convenient literals:
1 | // Go 1.20 |
Just a quick reminder: you can use a[:3]
instead of a[0:3]
. I mention this for clarity.
# 5. Package Initialization with “import _”
Sometimes, in a library, you might come across import statements combining an underscore (_) like this:
1 | import ( |
This executes the package’s initialization code (init function) without needing to create a named reference to it. This allows you to initialize packages, register connections, and perform other tasks before running the code.
Let’s better understand how it works with an example:
1 | // Underscore |
# 6. Importing with “import .”
After understanding how to import with an underscore, let’s see how more commonly used is the dot (.) operator.
As a developer, the dot (.) operator can be used to import exported identifiers from an imported package without specifying the package name. This is a useful shortcut for lazy developers, especially when dealing with long package names in a project, like externalmodel
or doingsomethinglonglib
.
To demonstrate, here’s a simple example:
1 | package main |
# 7. Go 1.20 Allows Merging Multiple Errors into a Single Error
Go 1.20 introduces new features to the errors
package, including support for multiple errors and changes to errors.Is
and errors.As
. One of the new functions added in errors
is Join
, which we’ll discuss in detail below:
1 | var ( |
If there are multiple tasks causing errors, you can use the Join
function instead of manually managing arrays. This simplifies the error handling process.
# 8. Checking if an Interface Is Truly Nil
Even if an interface holds a value of nil
, it doesn’t mean the interface itself is nil
. This can lead to unexpected errors in a Go program. Therefore, it’s important to know how to check if an interface is truly nil
.
1 | func main() { |
How do we determine if the interface{}
value is nil
? Fortunately, there’s a simple tool to help us with that:
1 | func IsNil(x interface{}) bool { |
# 9. Parsing time.Duration in JSON
When parsing JSON, using time.Duration
can be a cumbersome process because it requires adding 9 zeros (i.e., 1000000000) after the second. To simplify this process, I created a new type called Duration
:
1 | type Duration time.Duration |
To parse strings (like “1s” or “20h5m”) into an int64
type duration, I also implemented custom parsing logic for this new type:
1 | func (d *Duration) UnmarshalJSON(b []byte) error { |
However, it’s worth noting that the variable ‘d’ should not be nil, or it might result in marshaling errors. Alternatively, you can also check ‘d’ at the beginning of the function.
Useful and Versatile Go Code Snippets
https://blog-1so.pages.dev/Useful_and_Versatile_Go_Code_Snippets/