Help with simple, idiomatic Go

Hi new at go, here. I’d like some input on a super simple go function that reads a user’s config file and exits 1 if there’s some kind of problem opening/reading it. Eventually I will obviously parse the JSON, but being new to go, wanted to start off simple and see if I am on the right track idiomatically. Is this idiomatic, in that the programmer has to keep checking for errors as they go? I don’t like err2 variable name. I generally wouldn’t use pointers unless I needed to, and would rather return a string. Is this the idiomatic way to return an optional string? It seems unduly verbose to me.

Any comments are welcome.

package main

import (
  "fmt"
  "io/ioutil"
  "log"
  "os"
  "path"
)

// Attempts to read  the .sinkerrc.json file in the user's
// home directory as a pointer to a string.
func readSinkerRc() (*string, error) {
  homdir, err := os.UserHomeDir()
  if err != nil { 
              // problem getting homedir
  	return nil, err
  }
  dat, err2 := ioutil.ReadFile(path.Join(homdir, ".sinkerrc.json"))
  if err2 != nil {
              // Problem reading file.
  	return nil, err2
  }
  ret := string(dat)
      // return a pointer to the data so we could also return nil for error case.
  return &ret, err
}
func main() {
  dat, err := readSinkerRc()
  if err != nil {
  	log.Fatal("Problem reading your .sinkerrc.json file: " + err.Error())
  }
  fmt.Println(*dat)
}

Some thoughts: You don’t need to rename err, but yes, you should check for errors at each point an error could occur. I also took out the pointers and made sure strings are returned at each point.

func readSinkerRc() (string, error) {
  homdir, err := os.UserHomeDir()
  if err != nil { 
              // problem getting homedir
  	return "", err
  }
  dat, err := ioutil.ReadFile(path.Join(homdir, ".sinkerrc.json"))
  if err != nil {
              // Problem reading file.
  	return "", err
  }
  return string(dat), nil 
}
func main() {
  dat, err := readSinkerRc()
  if err != nil {
  	log.Fatal("Problem reading your .sinkerrc.json file: " + err.Error())
  }
  fmt.Println(dat)
}

Thanks!

I see so since we are only checking for err, no need to return nil for the string and using empty string simplifies and allows us to avoid pointer and type the first ret val as string.

It’s interesting that somewhat exceptional conditions are dealt with first in the flow. E.g. most of the time the system can read the user’s home dir. This is kind of opposite to what I’ve been taught generally, which is to deal with the normative case first.

In the case of go, we usually deal with error cases first? You could also check that err is nil, compute, then at the end deal with when it is not nil, but that’s generally not done?

That is how I usually see it.

Errors are values - The Go Programming Language argues that, as errors are values, you can write more specific error-handling code if you like.

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

// readSinkerRc reads the .sinkerrc.json file in the user's
// home directory.
func readSinkerRc() (string, error) {
	path, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}
	path = filepath.Join(path, ".sinkerrc.json")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(data), err
}

func main() {
	data, err := readSinkerRc()
	if err != nil {
		log.Fatal("Error reading .sinkerrc.json file: " + err.Error())
	}
	fmt.Println(data)
}