Re: Proposal: Error handling with else catch (else keyword)

176 views
Skip to first unread message
Message has been deleted

Brian Candler

unread,
Feb 16, 2020, 4:10:29 AM2/16/20
to golang-nuts
Code like this:

f, err := os.Open("filename.ext")
if err != nil{
    log
.Fatal(err)
}

would become:

f := os.Open("filename.ext")
else err != nil {
    log
.Fatal(err)
}

The `err` variable in the example above is automatically initialized to the last
return value of the function call `os.Open`.


I think this is a bad fit for Go. There's so much magic hidden in the second version:

* a function which returns N values can now be assigned to N-1 variables
* the Nth value is assigned to a magic variable, whose name is guessed from the following code block
* the error handling isn't even any shorter - and it definitely is much less clear

That's without considering the semantic implications.  An expression like "if err != nil", which is a conditional expression, sprouts an automatic assignment as well.  What if there are two variables in the expression, like "if err != eof" or "if eof != err".  Which of these variables gets magically assigned?  You already mentioned that problem towards the end of your post, and you propose limiting the available expression syntax, so it's not really a true expression any more.  What if you say "if nil != err"?  But I think the point is that the *reader* of the code shouldn't have to worry about such things.  The code should be obvious from inspection, and your proposal converts obvious code into non-obvious code.  And unlike throw/catch, you still have to do an explicit error check after every step.

The explicit error handling in go *can* arguably have the effect of distracting from the main happy-path logic, but I don't think this is an improvement.

Brian Candler

unread,
Feb 16, 2020, 5:30:37 AM2/16/20
to golang-nuts
One other thing: unless you change the entire syntax of the go language, your proposed "else" modifier needs to go on the same line as the statement it relates to.

lgo...@gmail.com

unread,
Feb 16, 2020, 1:28:00 PM2/16/20
to golang-nuts
Great suggestion
f := os.Open("filename.ext")
else err != nil {
    log
.Fatal(err)
}

is much needed syntax improvement vs the current syntax.

I would further suggest f ?= os.os.Open("filename.ext") : log.fatal("Cant open filename.ext")
But the GO 'custodians' get apoplexy whenever they see any syntax that resembles the C ternary operator.
Chill-out 'custodians' !!
'?' is just a symbol that can be replaced by some another one
On Saturday, February 15, 2020 at 6:06:39 PM UTC-5, teknoslo wrote:
This is more of a request for comments and not a complete proposal. I will try
to keep this short(ish) and simple.

Note that the `else` I will write about is not to be confused with the usual
`else` used with `if`. It is an `else` in a slightly different context.

`else` is basically an `if` with a variable initialized to the last return value
of a function call from the previous line.

Note: "from the previous line" is a simplification. Between the function call
and the `else` statement white space and comments are allowed, but nothing
else.

Another note: "function call" is also a simplification. It can be any statement
that returns a value. It could be, for example, the "comma ok" idiom used for
testing whether an entry in a map is present.

A few simple rules:
  • Only the last return value is ever taken, not multiple.
  • If there is no return value, `else` is not possible.
  • The variable is defined only within the scope of the `else` block.
  • The name of the variable can be any valid identifier.
  • Braces are mandatory.

Code like this:

f, err := os.Open("filename.ext")
if err != nil{
    log
.Fatal(err)
}

would become:

f := os.Open("filename.ext")
else err != nil {
    log
.Fatal(err)
}

The `err` variable in the example above is automatically initialized to the last
return value of the function call `os.Open`. The variable could have any valid
variable name and is defined only in the `else` block.

I like to read the example above with an implicit try before the function call.
I.e., try opening filename.ext, else if err is not nil... For this reason, I
call this construct else catch.

A longer example from Error Handling - Problem Overview:

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)
    }
}

would become:

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

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

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

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

By using the else catch we declutter the function calls, more specifically
assignments, thus improving readability. Error handling stays familiar,
explicit, and simple.

And since the variable in else catch is defined only in the `else` block, we do
not need to re-assign the `err` variable again and again, making the code more
obvious and unambiguous.

I would like to note that the else catch example is longer, but I think length
should not be equated with readability.



Else catch is not meant to replace the `if err != nil` idiom, but rather
complement it. It would allow for a clear separation of return values we "care"
about and error handling. Or put another way, it would allow for a clear
separation of a happy and an unhappy path of execution.



What about the error handling blocks?

We should not try to stash away error handling. Most gophers are already used to
skipping error handling blocks while reading Go. Using else catch would make
that even easier. IDEs could collapse else catch blocks on command.



Else catch is orthogonal and can be used for anything, not just error handling.
We just have to remember that it always takes the last (and no more) return
value of a function call from the previous line.

For example, we can test for existence of a key:

m := map[string]int{
    "route": 66,
}
i
:= m["route"]
else !ok {
    i
= -1
}



Now, there is a slight problem with current else catch. If it is basically an
`if`, what happens if we use multiple variables in the condition? Which variable
came from the last return value?

firstTimeBonus := true
apples
:= map[string]int{
    "bob": 7,
    ...
}
apples
["john"]
else !ok && firstTimeBonus {
    apples
["john"] = 3
}

In this case we can probably figure out that variable `ok` is the last return
value of `apples["john"]`. But it is not explicitly stated that `ok` came from
the last return value. What if `ok` variable was already declared and we
unknowingly shadowed it? And how would the compiler know to which variable to
assign the last return value?

This could be solved by allowing only one variable in the `else` condition.
Then the example above would be invalid, which it should be, because the if
statement would be more appropriate. Also if there was only one variable allowed
in the condition, there would be no confusion as to where the variable came
from.



I believe something like else catch could be beneficial to Go, mainly improving
readability. But there are a few problems to consider.

`else` would have different semantics depending on the context. This should not
be a problem for people, but it could be for the compiler and other tools.

Go would have a new, not widely known construct. How much would it impact the
learning curve of the language?



This is just an idea and I am interested in what you think.
  • Would else catch be useful to you?
  • Could it be simpler? Maybe a keyword or a built-in that could take the last return value and we could use if with a short statement instead of `else`.
  • Do you see a problem with it? Does it feel too much like magic?
  • Is it trying to solve a problem you even have?
  • Do the pros outweigh the cons?

Kevin Chadwick

unread,
Feb 16, 2020, 2:21:21 PM2/16/20
to golan...@googlegroups.com
I prefer the clarity of the current syntax. What is the obsession with LOC.

>I would further suggest f ?= os.os.Open("filename.ext") :
>log.fatal("Cant
>open filename.ext")
>But the GO 'custodians' get apoplexy whenever they see any syntax that
>resembles the C ternary operator.
>Chill-out 'custodians' !!

I never realised, Thank you custodians! I have always hated ternary one liners. Much as I dislike needless methods and structs and especially putting too much into a go one liner with embedded functions that need extra consideration/concentration before moving on.

disroot

unread,
Feb 16, 2020, 8:04:48 PM2/16/20
to golan...@googlegroups.com
Right? I’m with you here. So thankful with the custodians that prefer cleaner syntax over complex and shorter ones.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/0406AD8C-3A84-4C19-ADA2-5E646B23F1A2%40gmail.com.

MUNGAI NJOROGE

unread,
Feb 17, 2020, 1:27:36 AM2/17/20
to disroot, golang-nuts
Maybe an eye opener, but why is everyone obsessed with the try..catch.. Or try..catch..else error handling? Are we trying to copy idioms from another language?

The try..catch..finally in java has always played part in newbie bugs. They tend to skip the error instead of dealing with it.

I prefer the simple and straight forward implementation in go. That way, the developer is in control of all the exceptions. 

Reply all
Reply to author
Forward
Message has been deleted
Message has been deleted
Message has been deleted
0 new messages