Simulating Try-Catch in Go (Golang)

In the Go code I’ve been writing to help learn the language, I’ve been using a common function to deal with error conditions. The function terminateOnErr(err error) checks to see if the err parameter is nil. If not, it displays the paramter and calls os.Exit(1).

func terminateIfError(err error) {
   if( err != nil ) {
      fmt.Fprintln(os.Stderr, err)
      os.Exit(1)
   }
}

Later in the code, I use the following line after every error check that I need to perform:

f,err := os.Open(filename)
terminateIfError(err)

I realize that it’s not practical to always terminate immediately on the occurrence of the first error, but this approach allowed me to apply a common error-handling routine in my early programs.

Today, I wondered what would happen if I didn’t exactly want to terminate the program immediately. Was there a way that I could simulate a try-catch construct from other languages?

The first practice that I intend to observe is to call a function that will check an error variable to see if it’s nil. If it’s not nil, this new function should display the caller’s filename and line-number on the stderr output device. It should then return true to the caller. If the error variable is nil, it should display nothing and should return false:

func dispErr(err error) bool {
   if(err!=nil) {
      _,file,line,_:=runtime.Caller(1)
      fmt.Fprintf(os.Stderr,"File: %s\nLine: %d\nErr: %v\n",file,line,err)
      return true
   }
   return false
}

Note that using runtime.Caller(1), you receive a filename and the line-number among other characteristics of the calling code. This is akin to using the C macros __FILE__ and __LINE__ except that it occurs at runtime and can be used to trace as far back up the call-chain as possible. Note that runtime.Caller(0) returns the current file and line-number rather than the caller’s filename and line-number.

My intent was to then create and invoke an anonymous function:

(func() {
   // code here
})()

I would use this function as a try block. All I have to do to drop out of the function is to execute a return statement when an error condition arises.

As each potential error variable is returned from function calls, I can now check the error variable with dispError() … which will then output the filename, line-number, and error message … and can then invoke the return statement, halting any further execution of the body of the anonymous function ( try-block ).

if dispErr(err) { return }

I can copy the above line below every place in the anonymous function where the err variable might change value.

I built two dummy functions … dummy1() and dummy2() … which attempt to open files and return error variables if they are unable to do so. dummy1() attempts to open a file called “dummy.one” and dummy2() attempts to open a file called “dummy.two”. Here’s how I simulate a try-catch block by using the techniques discussed above:

// Copyright 2013 - by Jim Lawless
// License: MIT / X11
// See: http://www.mailsend-online.com/license2013.php
//
// Bear with me ... I'm a Go noob.
 
package main
 
import (
   "fmt"
   "runtime"
   "os"
)
 
   // Try to open a file named dummy.one
   // for input.  Return the error if it won't open.
func dummy1() error {
   f,err:= os.Open("dummy.one")
   if(err!=nil) {
      return err
   } else {
      f.Close()
      return nil
   }
}
 
   // Try to open a file named dummy.two
   // for input.  Return the error if it won't open.
func dummy2() error {
   f,err:= os.Open("dummy.two")
   if(err!=nil) {
      return err
   } else {
      f.Close()
      return nil
   }
}
 
   // If the error value is not nil,
   // display the caller's filename and
   // calling line-number.  Then, display
   // the error object.  Return true
   // if the err parameter was true.
   // Otherwise, return false.
func dispErr(err error) bool {
   if(err!=nil) {
      _,file,line,_:=runtime.Caller(1)
      fmt.Fprintf(os.Stderr,"File: %s\nLine: %d\nErr: %v\n",file,line,err)
      return true
   }
   return false
}
 
func main() {
   var err error
 
      // Create an anonymous function and
      // invoke it.
      //
      // Use conditional returns to drop out
      // of the function block to simulate falling
      // out of a "try" block.
      //
   (func() {
      fmt.Println("Attempt #1")
      err=dummy1()
      if dispErr(err) { return }
 
      fmt.Println("Attempt #2")
      err=dummy2()
      if dispErr(err) { return }
   })()
 
   if(err!=nil) {
         // catch block equivalent
      fmt.Println("In 'catch' block")
   }
      // finally block equivalent
   fmt.Println("Done!")
}

I have ensured that I do not have a file named “dummy.one” nor do I have one named “dummy.two” in my current directory. Here’s the output of the above “tryit.go” program:

Attempt #1
File: C:/j/godev/tryit.go
Line: 68
Err: open dummy.one: The system cannot find the file specified.
In 'catch' block
Done!

We only made it to the invocation of dummy1(). The error test immediately underneath detected the file-not-found condition and returned from the inlined anonymous function. We did not reach the invocation of dummy2() in our pseudo try-block.

Let’s create the file dummy.one in the current directory using a text-editor or an echo command. Now, let’s run the tryit program again.

Attempt #1
Attempt #2
File: C:/j/godev/tryit.go
Line: 72
Err: open dummy.two: The system cannot find the file specified.
In 'catch' block
Done!

Note that we now made it past the first attempt and made it on to the call to dummy2() … which then experienced a post-call error condition.

If we also create the file dummy.two in our current directory and run one more time, we see the output:

Attempt #1
Attempt #2
Done!

The test above did not cause the “catch” block to be invoked because the err variable held the value nil upon exit of the anonymous function.

I am still defining my Go coding style and am not sure that I want to code new if-statements for each possible error. I’d rather have some boilerplate code that I can paste in after each call that isn’t too intrusive.

I have yet to use the anonymous function technique in any “real” Go code, but it’s something I will be trying to use early to see if it has merit in real-world code.

Lessons learned via community feedback:

  • Use go fmt to format my code.
  • Interesting approach, but simple if checks are preferred.
  • Use go fmt to format my code.
  • If try/catch is needed, investigate defer, panic, and recover.
  • Use go fmt to format my code.
  • Watch the overuse of C-style parens.