Skip to content

proposal: Go 2: Elvis Operator #25632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
deanveloper opened this issue May 29, 2018 · 16 comments
Closed

proposal: Go 2: Elvis Operator #25632

deanveloper opened this issue May 29, 2018 · 16 comments
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@deanveloper
Copy link

deanveloper commented May 29, 2018

Yet another solution for error handling TM

The Elvis Operator ?:

Seeing all of the solutions to error handling, here is what matters: if err isn't nil, what do we do?

So therefore I propose the following syntax, the "elvis operator": ?:. It evaluates an expression on it's left, and if the left side is not nil, the return statement on the right is executed.

The grammar would be Expression "?:" ReturnStmt. Note that because the right-hand side is ReturnStmt, the Elvis Operator is not a standard binary expression.

Also, the syntax is inspired by Kotlin (although Kotlin's is a true binary operator), which is in turn inspired by C's ternary operator.

The decision to make the right side a ReturnStmt instead of an ExpressionList (which would reduce boilerplate code) is made because all exits of a function should have a return. This makes it less confusing for a reader and more clear as to what it does.

Error Handling

err := io.SomeIoOperation()

// possible ways to handle the error:

err ?: return err
// AKA if err != nil { return err }

err ?: return &MyErrorWrapper{"info", err}
// AKA if err != nil { return &MyErrorWrapper{"info", err} }

err ?: return "", 0, err
// AKA if err != nil { return "", 0, err }

This is superior to #21161 because it has much less implicit behavior. The return is shown, and err is previously defined. It is also more flexible, and can be used in other cases...

Argument Verification

func ErrorIfNil(arg interface{}) error {
    arg ?: return ArgumentIsNilErr
    // equivalent: if arg != nil { return ArgumentIsNilErr }
    return nil
}

Advantages

  • This is simple, readable, and doesn't have much implicit behavior.
  • The return is explicit, so it's obvious where the function exits.
  • It handles multi-return and error wrappers extremely well.
  • Intuitive: err ?: return err just look like "if there is an error, return it".
@gopherbot gopherbot added this to the Proposal milestone May 29, 2018
@deanveloper
Copy link
Author

deanveloper commented May 29, 2018

A possible revision to this is to change the grammar to Expression "?:" Statement, where the statement MUST be either a ReturnStmt or a function/method call. (Why is there no special grammar for function/method calls? They are used for multiple statements)

Either way, this would allow for the err ?: panic(err) pattern, which could be useful as well, although (in my opinion) panic should be avoided, so it may be better to let it require the if err != nil pattern

@rsc rsc added the v2 An incompatible library change label May 29, 2018
@leeola
Copy link

leeola commented May 29, 2018

For Go simplicity sake, I think I'd prefer if gofmt allowed:

func ErrorIfNil(arg interface{}) error {
    if arg != nil { return ArgumentIsNilErr }
    // instead of: arg ?: return ArgumentIsNilErr
    return nil
}

Your proposal saves lines, sure, but at the cost of new syntax. If on the other hand gofmt allowed the above, you save the same number of lines while reducing new syntaxes/etc.

@isaachier
Copy link

I think zig got it right with the try keyword.

result = try openFile(f)

Essentially equivalent to

result, err := openFile(f); err != nil {
    return err
} 

@mrkaspa
Copy link

mrkaspa commented May 29, 2018

@isaachier I like that

@ianlancetaylor ianlancetaylor added the LanguageChange Suggested changes to the Go language label May 29, 2018
@zevdg
Copy link
Contributor

zevdg commented May 29, 2018

The zig try expression is great for the simple case of if err != nil { return err }, but what about the other 2 cases that were called out in this proposal?. The if err != nil { return "", 0, err } case could be handled by returning the error in the last position and returning a zero value for any other return values.

It's the if err != nil { return &MyErrorWrapper{"info", err} } case that is of the most concern to me. Any proposal that simplifies passing an error up the call stack as-is, but does not provide a mechanism to wrap that error in appropriate context before passing it up the stack would have the unfortunate side effect of discouraging people from adding context to their errors. AFAICT, the zig try expression doesn't handle this very important case.

@adamdecaf
Copy link
Contributor

adamdecaf commented May 29, 2018

Any proposal that simplifies passing an error up the call stack as-is, but does not provide a mechanism to wrap that error in appropriate context before passing it up the stack would have the unfortunate side effect of discouraging people from adding context to their errors.

This is how I feel about these return err proposals too. It's rarely the case when just returning the error without context is the best idea.

Usually just adding a filename, domain, exit code, timeout / duration gives err crucial detail.

@isaachier
Copy link

OK I simplified the zig case. In that language, the error type is specified as part of the function return type (fn openFile(name: []const u8) File!IOError).

@mattn
Copy link
Member

mattn commented May 30, 2018

I do not agree to put in new operators, but at least new operator's subject should be return because it have two different AST (expression/statement). And this operator doesn't reduce the AST.

@isaachier
Copy link

@zevdg luckily zig has built in stack traces (that look much better than Go's sadly cryptic stack traces IMO).

@as
Copy link
Contributor

as commented May 30, 2018

No thank you, no thank you very much.

@josharian
Copy link
Contributor

@as if you don't have new information or technical argumentation to add, please express such opinions using the GitHub voting mechanism. See https://github.com/golang/go/wiki/NoMeToo. Thanks.

@adg
Copy link
Contributor

adg commented May 30, 2018

A common idiom:

f, err := os.Open(file)
if os.IsNotExist(err) {
  return nil
}
if err != nil {
  return err
}

becomes:

f, err := os.Open(file)
if os.IsNotExist(err) {
  return nil
}
err ?: return err

I find the asymmetry there pretty strange here. To address that, you could suppose ?: would also execute the statement to its right if its left is not a zero value. Then you could write:

f, err := os.Open(file)
os.IsNotExist(err) ?: return nil
err ?: return err

I prefer the original Go code to either of these examples.

@fzipp
Copy link
Contributor

fzipp commented May 30, 2018

you could suppose ?: would also execute the statement to its right if its left is a zero value (not just nil)

Shouldn't this be "if its left is not a zero value"?
I'd prefer just "?" instead of "?:". To me the colon indicates an "else".

f, err := os.Open(file)
os.IsNotExist(err) ? return nil
err ? return err

Also the "Argument Verification" example (test for == nil) does not match the semantics established in the "Error Handling" section (test for != nil).

@metakeule
Copy link

Why does nobody notice the obvious error in

func ErrorIfNil(arg interface{}) error {
    arg ?: return ArgumentIsNilErr
    // equivalent: if arg != nil { return ArgumentIsNilErr }
    return nil
}

This is plain wrong, it must be

func ErrorIfNotNil(arg interface{}) error {
    arg ?: return ArgumentIsNotNilErr
    // equivalent: if arg != nil { return ArgumentIsNotNilErr }
    return nil
}

Which clearly shows that it's not able to do argument verification (it is very rare that someone wants to verify that an argument should be nil). It also shows that the symbol is confusing, if even the author is not able to use it according to his own definition.

@adg
Copy link
Contributor

adg commented May 30, 2018

@fzipp:

Shouldn't this be "if its left is not a zero value"?

Right.

@deanveloper
Copy link
Author

My proposal clearly has a lot of design flaws (ie Expression "?:" ReturnStmt is really strange) and I've made quite a few errors making it, haha. I'm going to close this one out and instead promote this one!

I think #25626 is much more simple and readable than #21161, and it's pretty intuitive, so I'd highly recommend checking it out!

@golang golang locked and limited conversation to collaborators May 31, 2019
@bradfitz bradfitz added the error-handling Language & library change proposals that are about error handling. label Oct 29, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests