8000 proposal: Go 2: error handling with error receiver function · Issue #36338 · golang/go · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
proposal: Go 2: error handling with error receiver function #36338
Closed
@drupsys

Description

@drupsys

Hello everyone,

What if a function that receives only an error could be used as an error receiver for a failed function call?

func errorReceiver(err error) error {
    return fmt.Errorf("some error: %v", err) // do something
}

OK this may be completely crazy also maybe someone already suggested something similar but hear me out...

Overview

// Higher order function that returns an error receiver
func errorHandler(src string) (func(error) error) {
    return func(err error) error {
        return fmt.Errorf("Failed to open file %s: %v", err)
    }
}

func example(src string) error {
    errorReceiver := errorHandler(src)
    r := os.Open(src) or errorReceiver

    // do something with r

    return nil
}

Assume that os.Open fails with an error, since the signature of errorReceiver takes an error as argument the errorReceiver could be called with the os.Open error. Lets see what that would do to the order of execution.

  1. r := os.Open(src) or errorReceiver
  2. os.Open(src) returns nil, fmt.Error("Some error")
  3. call errorReceiver(fmt.Error("Some error"))
  4. If result of errorReceiver is still an error then assign errorReceiver's error to example's return error and exit from example
  5. If result of errorReceiver is nil then continue execution of example

With only one new language concept we are able to reusable handle errors e.g.

func errorHandler(src, dst string) (func(error) error) {
    return func(err error) error {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
}

func CopyFile(src, dst string) (err error) {
    errorReceiver := errorHandler(src, dst)

    r := os.Open(src) or errorReceiver
    defer r.Close()

    w := os.Create(dst) or errorReceiver
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst) // only if a “try” fails
        }
    }()

    io.Copy(w, r) or errorReceiver
    w.Close() or errorReceiver

    return nil
}

If in addition to this go was also able to infer the return type of higher order functions then you could end up with pretty clean syntax for error handlers. The above error handler rewritten would look something like.

// The return type of func errorHandler(src, dst string) is implied to be func(error) error
func errorHandler(src, dst string) => func(err error) error {
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
}

Although even without it, this would be a significant improvement.

Handling errors and recovering from them

What if you actually want to handle the error not just simply enrich it? A more sophisticated implementation of error receiver could be implemented. So far all examples of error receiver returned an error, lets go back to my initial example.

func errorHandler(src string) (func(error) error) {
    return func(err error) error {
        return fmt.Errorf("Failed to open file %s: %v", err)
    }
}

What if error receiver here return just the *File, which is the first return value of os.Open, e.g.

func errorReceiver(err error) *File {
    return &File{} // return some default file instead
}

func example(src string) error {
    r := os.Open(src) or errorReceiver

    // do something with r

    return nil
}

Since errorReceiver is no longer returning an error but the expected value of os.Open then the result of errorReceiver could just be assigned to r and the execution of the example could continue, this effectively handles the error.
Note that if os.Open returned more than one non error value e.g. func Open() (T1, T2, ...Tn, error) then error receiver would have to have the following signature func(error) (T1, T2, ...Tn)

More concretely error receiver would have the following valid signatures:

  • func(error) error - Error receiver that always results in a failure
  • func(error) T1, T2, ...Tn - Error receiver that always results in recovery
  • func(error) T1, T2, ...Tn, error - Error receiver that may recover or fail

Final example to show a case where an error receiver itself might recover or fail

func errorHandler(user string) (func(error) error) {
    return func(err error) error {
        return fmt.Errorf("User %s profile could not be found: %v", user, err)
    }
}

func downloadProfileFromAnotherSource(user string) (func(error) (Profile, error)) {
    return func(err error) (Profile, error) {
        profile := serviceB.Find(user) or errorHandler(user)
        return profile, nil
    }
}

func DownloadProfileA(user string) (Profile, error) {
    profile := serviceA.Find(user) or downloadProfileFromAnotherSource(user)
    return profile, nil
}

Conclusion

I think this is a rather neat approach since the only new thing added here is, admittedly, a slightly bizarre behaviour of error receiver when it returns, although the official try(...) error handling proposal was effectively doing the same thing, everything else here is just basic functions. Or am I just having brainfarts? Anyway, I hope someone will find this interesting.

Thanks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeLanguageChangeSuggested changes to the Go languageProposalWaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.error-handlingLanguage & library change proposals that are about error handling.v2An incompatible library change

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0