DEV Community

Arthur Christoph
Arthur Christoph

Posted on • Edited on

Go Series: Defer, Finally...

Golang Logo

Defer is commonly used in Go where in other languages use finally or ensure block, such as cleaning up resources. Defer runs right before the enclosing function is exiting.
Other languages like Python, JS, Ruby to name a few, can use finally try-catch-finally block to handle cleanup.
Note the flat vs nested nature between go and other languages. Idiomatic Go always strives for flat structure when possible : defer, goroutine, returning error as a value of a function, etc.

In Go:

defer file.close()
Enter fullscreen mode Exit fullscreen mode

In JS:

try {
}
catch {
}
finally{
  file.close()
}
Enter fullscreen mode Exit fullscreen mode

Cleaning up resources is a very good case for defer and in Go, this is handled gracefully with just a single statement defer. Other languages resort to a different construct for more elegant solution as we see below. Let's compare the file reading between Go, JS, and Python.

In Python, it has try finally construct as below

file = open('a.txt', 'r') 
try: 
  for line in file:
    print(line)
finally: 
  file.close() 
  print('finally', file.closed)
Enter fullscreen mode Exit fullscreen mode

However, Python has 'with' to deal with context management such as closing file. A close function is implicitly called.

with open('a.txt', encoding="utf-8") as f:
  line = f.readline()
  print(line)
Enter fullscreen mode Exit fullscreen mode

In JS, it relies on NodeJS methods to open file, whether with callback, sync or async methods. The cleanup is handled automatically by the methods.

// readFile / readFileSync opens a file and automatically close it
fs.readFile('a.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return
  }
  console.log(data)
})

const data = fs.readFileSync('a.txt','utf8')
Enter fullscreen mode Exit fullscreen mode

In Go, reading file would be in this order:

  1. Open the file
  2. Defer the close file
  3. Do something with the file data

One important characteristic of defer in idiomatic Go is: placing the deferred resource closing function right after the opening resource function

f, err := os.Open("a.txt")
defer f.Close()
if err != nil {
  log.Fatal(err)
}
// read the file line by line using scanner
scanner := bufio.NewScanner(f)
for scanner.Scan() {
  // do something with a line
  fmt.Printf("%s\n", scanner.Text())
}
Enter fullscreen mode Exit fullscreen mode

You can place the defer at the very end of the function but avoid doing this for two reasons:

  1. The point of placing right after opening resource is so that you remember that you will close file whatever happens
  2. It can give a misconception that it may not be run, as it looks like a normal statement. An inexperienced reader of the code might think that it will return without invoking the f.Close(). Especially when it is a way down in a long method that it is hard to read/confirm if the closing function does exist, as shown below.
func readFile() error { 
f, err := os.Open("a.txt")
if err != nil {
  return err
}
// Many line of statements can hide the defer statement below
defer f.Close()
}
Enter fullscreen mode Exit fullscreen mode

Another common defer usage in Go is for db transaction. Using database/sql package, defer can be used for conditionally running transaction method based on an error existence.

defer func() {
  if err == nil {
    err = tx.Commit()
  }
  if err != nil {
    tx.Rollback()
  }
}()
Enter fullscreen mode Exit fullscreen mode

Defer is Go is a powerful construct that gives the language a simple, elegant and robust way to handle closing of resources, when the closing must be done regardless of the conditions in the enclosing function.

Top comments (0)