Skip to content

proposal: Go 2: add "or err: statement" after function calls for error handling #33029

Closed
@urandom

Description

@urandom

I'd like to draft the following proposal for an expression used for error handling. I believe it achieved the following goals :

  1. It reduces verbosity associated with error handling
  2. It provides separation between business and error logic without hiding any
  3. It is intuitive and should be easy to understand even without prior knowledge.
  4. it clearly shows what is about to happen in any branch.
  5. Useful in functions that do not return errors - such as http handlers.

For disclosure, while I do think the try proposal needs improvement, it is on the right track as it covers the first two of the aforementioned points. I also don't think this will get any traction, though I do hope for some discussion, to at least know it was considered.

Design

The draft introduces a new syntactic form: an optional or <identifier>: <Statement> expression added to the Return, Expression, Assignment and ShortValDecl statements.

Edit: after the first feedback, perhaps the grammar should be changed to or <identifier> <block> to better align with current syntax constructs.

This expression works similarly to try or check. If the preceding expression list produces one or more values, the last value is checked whether it is different than its zero value. If it is not, any previous values except it will be returned or assigned. If the last value is nonzero, it will be assigned to the identifier to be used in the scope of the following statement.

The now well known function can be rewritten as:

func CopyFile(src, dst string) error {
	r := os.Open(src) or err: return err
	defer r.Close()

	w := os.Create(dst) or err: return err
	defer w.Close()

	io.Copy(w, r) or err: return err

	w.Close() or err: return err
}

Edit:

func CopyFile(src, dst string) error {
	r := os.Open(src) or err {
              return err
        } 
	defer r.Close()

	... 
}

Since the trailing part is a statement in itself, it can be used to decorate errors, handle them (in a code block if needed), breaking and continuing from loops, etc. It also doesn't allow nesting of invocations, which would otherwise reduce readability IMHO.

Drawbacks

One obvious drawback is that it will be more difficult to implement and handle by various tools that a function.
It also may or may not be backwards compatible. Current keywords cannot be used as identifiers,
however this implementation could avoid defining or as another keyword. I do not have enough knowledge to know this if feasible.

Addendum

While everything is up for debate, I haven't so far restricted the last value to only be of the error type. This construct seems handy when obtaining a value from a map as well - either obtaining it or dealing with its absence.

Edit:

Example:

func A(m map[string]interface{}) {
    val := m["key"] or !ok {
        log.Println("Key not found")
        return
    }

   data := val.(MyType) or !ok {
       log.Println("Key value is not of MyType")
       return  
   }

  ...
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions