Skip to content

Instantly share code, notes, and snippets.

@bhenderson
Last active September 10, 2019 13:26
Show Gist options
  • Save bhenderson/32a86f3315ac109dd0ce5a9884c50f83 to your computer and use it in GitHub Desktop.
Save bhenderson/32a86f3315ac109dd0ce5a9884c50f83 to your computer and use it in GitHub Desktop.
Error handling can already be simplified by introducing single-line conditionals, which were already rejected.

These are my thoughts on the Go 2 draft for error handling.

The example go program they give is:

func CopyFile(src, dst string) error {
	r, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	if _, err := io.Copy(w, r); err != nil {
		w.Close()
		os.Remove(dst)
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	if err := w.Close(); err != nil {
		os.Remove(dst)
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}
}

But I imediately saw two issues, 1) The error formatting can be extracted out to a defer function and 2) We can extract out the close logic

func CopyFile(src, dst string) (err error) {
        defer func() {
                if err != nil {
                        err = fmt.Errorf("copy %s %s: %v", src, dst, err)
                }
        }()

        r, err := os.Open(src)
        if err != nil {
                return err
        }
	defer r.Close()
	
        w, err := os.Create(dst)
        if err != nil {
                return err
        }

        _, err = io.Copy(w, r)
        ec := w.Close()
        if err == nil {
                err = ec
        }
        if err != nil {
                os.Remove(dst)
        }
        return err
}

Now, what I see as the remaining problem is people wanting to address the number of lines. I've always longed for oneline conditionals (coming from ruby) The code shortens to

func CopyFile(src, dst string) (err error) {
        defer func() {
                err = fmt.Errorf("copy %s %s: %v", src, dst, err) if err != nil
        }()

        r, err := os.Open(src)
        return err if err != nil

        w, err := os.Create(dst)
        return err if err != nil

        _, err = io.Copy(w, r)
        ec := w.Close()
        err = ec if err == nil
        os.Remove(dst) if err != nil
        return err
}

This is now 1 line shorter than the proposed solution and all we did was remove multi-line conditionals. No new keywords.

We could further simplify by allowing implicit nil checks (I have no idea how hard this would be technically)

func CopyFile(src, dst string) (err error) {
        defer func() {
                err = fmt.Errorf("copy %s %s: %v", src, dst, err) if err
        }()

        r, err := os.Open(src)
        return err if err

        w, err := os.Create(dst)
        return err if err

        _, err = io.Copy(w, r)
        err = w.Close() || err
        os.Remove(dst) if err
        return err
}

(This last point goes a little beyond the draft, but it's also something I've always wanted!)

I also would like to bear in mind something that Rob Pike talked about a while ago (sorry can't find the link) about how programming languages are converging and I want to be careful about making go look more like ruby (or whatever)

I look forward to everyone's feedback.

@bhenderson
Copy link
Author

Thanks @barisere for your detailed response! I'm working on my response, but busy, so hopefully I can respond soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment