Error handling

627 views
Skip to first unread message

DrGo

unread,
Jul 30, 2023, 5:57:15 AM7/30/23
to golang-nuts
I looked at the long list of proposals to improve error handling in go but I have not seen the one I am describing below. If I missed a similar , can you pls direct me to where I can find it. If not what do you think of this approach. 

This involves introducing a new keyword "orelse" that is a syntactic sugar for an "if err!=nil" block.

The example code in Russ Cox's paper[1] will look something like this:

func CopyFile(src, dst string) error {

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

defer r.Close()

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

defer w.Close()

  err = io.Copy(w, r) orelse return err

err = w.Close() orelse return err

}


It is an error to not return an error from an orelse block.

In my eyes, this has the same explicitness and flexibility of the current style but is significantly less verbose. It permits ignoring the error, returning it as is or wrapping it. Because orelse is not used for any other purpose, it would be easy for reviewers and linters to spot lack of error handling.  

It also works well with named returns. e.g., 

func returnsObjorErro() (obj Obj, err error) {

  obj, err := createObj() orelse return  //returns nil and err

}


otherwise orelse is like "else" so e.g., it can be followed by a block if additional cleanup or error formatting etc is needed before returning, eg 
w, err := os.Create(dst) orelse {
  ....
  return err 
}

Similarity to "else" hopefully means that it is easy to learn. It is obviously backward compatible  

What do you think?

Brian Candler

unread,
Jul 30, 2023, 7:02:04 AM7/30/23
to golang-nuts
Just to be clear: are you hard-coding the variable name "err" into the semantics of "orelse"?  That is, you can't assign the error return to a variable of any other name?

I disagree that this makes the job of linters any easier than it is today.  For example, if you'd written

        ...

   err = io.Copy(w, r)

err = w.Close() orelse return err

}

then you'd still need to detect "value assigned but not used" in the linter (assuming it doesn't become *compulsory* to use "orelse" on any assignment to a variable called "err")

Brian Candler

unread,
Jul 30, 2023, 7:31:09 AM7/30/23
to golang-nuts
On Sunday, 30 July 2023 at 06:57:15 UTC+1 DrGo wrote:
I looked at the long list of proposals to improve error handling in go but I have not seen the one I am describing below.

There is a meta-ticket here: https://github.com/golang/go/issues/40432

Under the section "Simplifications of if err != nil, to reduce boilerplate" I found a link to
which seems very similar to yours, using keyword "onErr" after a semicolon, instead of "orelse".

if c, err := fn(); onErr { return 0, err }

DrGo

unread,
Jul 30, 2023, 8:40:25 AM7/30/23
to golang-nuts
orelse must return an error (ie satisfies the error interface); the specific type and variable name do not matter.
although on second thought.. I am not sure that even that restriction is necessary. 

DrGo

unread,
Jul 30, 2023, 8:45:54 AM7/30/23
to golang-nuts
thanks  for the link.
unlike the onErr approach, my proposal does not treat create special-status identifier; the orelse block is like any other else block

Brian Candler

unread,
Jul 30, 2023, 9:20:12 AM7/30/23
to golang-nuts
On Sunday, 30 July 2023 at 09:40:25 UTC+1 DrGo wrote:
orelse must return an error (ie satisfies the error interface); the specific type and variable name do not matter.

But how does "orelse" perform the check in the first place? Does it look for the variable named in the return statement?

var foo error
r, bar := os.Open(src) orelse return foo   // does this do "if foo != nil { return bar }" ??

Also, you are explicitly allowing bare return.  In that case, does it look at the return values in the function definition?  If the function returns multiple values, how does it know which one?

Brian Candler

unread,
Jul 30, 2023, 9:43:29 AM7/30/23
to golang-nuts
Typo: should have said

var foo error
r, bar := os.Open(src) orelse return foo   // does this do "if foo != nil { return foo }" ??

Harri L

unread,
Jul 30, 2023, 2:17:31 PM7/30/23
to golang-nuts
IMHO, you have used the irrelevant example (== 2nd code block) from Russ Cox's paper. The paper says:
This code is not nice, not clean, not elegant, and still wrong: like the previous version, it does not remove dst when io.Copy or w.Close fails.

I want to compare your proposal with the third example from the paper, which does (proper) error annotation and cleanup. Thanks.

DrGo

unread,
Jul 30, 2023, 4:39:26 PM7/30/23
to golang-nuts
Apologies if I am not understanding your question, but does not the compiler know the types of all variables (including the returned variables)? The compiler will emit code to execute the orelse block if the error variable (regardless of its name or concrete type) in the preceding statement is not nil; not very different from how it now handles an explicit if err!=nil. 

DrGo

unread,
Jul 30, 2023, 4:55:34 PM7/30/23
to golang-nuts
Good point Harri,

This is what the correct version will look like using this proposal 

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

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

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

In a more complex func, the error formatting/handling code can be further deduplicated by extracting it into a closure. 
e.g., 

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

w, err := os.Create(dst); orelse return copyErr(err)
err := io.Copy(w, r) orelse {
w.Close()
os.Remove(dst)
return copyErr(err)
}

err := w.Close() orelse {
os.Remove(dst)
return copyErr(err)
}
}

Brian Candler

unread,
Jul 30, 2023, 7:51:49 PM7/30/23
to golang-nuts
err := io.Copy(w, r) orelse {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}

My question still stands. Semantically, what value exactly does the "orelse" condition test is not equal to nil?

- does it test the value from the preceding assignment? If so, is "orelse" only valid immediately following an assignment expression? The original posting didn't say this.  And if it *is* linked to an assignment expression which assigns multiple values, does it only look at the last value? (Again, that was not specified)

- does it always test a variable called "err"? The original posting said it was equivalent to "if err!=nil" but a later post contradicted this

- does it test the value from the 'return' expression at the end of the block following orelse? Except in this case, it can't because it's buried inside fmt.Errorf

Jeremy French

unread,
Jul 31, 2023, 12:02:57 AM7/31/23
to golang-nuts
Also, errors are values, which means - although uncommon - a function could return two or more error values.  Which would orelse evaluate?  Even if you arbitrarily chose one, that would violate the explicit vs implicit code flow principle.  

My sympathies, OP.  I too hate the "if err!= nil" boilerplate, and have suggested my own idea for fixing it, which was similarly dismantled for good reasons by those more knowledgeable than me.  The truth is, this problem/issue has so many restrictions placed on it (currently idiomatic principles, backwards compatibility promise, explicit vs implicit, etc) that the set of possible solutions is VERY narrow, possibly infinitely so.

DrGo

unread,
Jul 31, 2023, 2:14:58 AM7/31/23
to golang-nuts
Thanks...
yes indeed. Too many requirements but I think this solution comes close to meeting them. If a rare function returns more than one error value (yet to see one in the wild) then the compiler should reject orelse use and the user can fallback on the (the if err!= nil) approach. 

DrGo

unread,
Jul 31, 2023, 2:23:30 AM7/31/23
to golang-nuts
the compiler which knows everything there is to know about the return value(s) of the function called in the same line as orelse can check if one of these satisfies the error interface (very rarely that there will be more than one) and check if its value is not nil and if so execute the orelse block. So yes, orelse is by design only valid after an assignment; the idea is that the presence of oresle signals possible change in program flow due to an error (this meets the explicitness requirement of the desired solution).   

Why does it need to test the return value in the orelse block? If handling the error raises errors then these errors will have their own oresle blocks which will return to the calling oresle block.

DrGo

unread,
Jul 31, 2023, 2:32:01 AM7/31/23
to golang-nuts
Another possibility Jeremy is that the orelse block is executed if any of the returned error values is not nil. 

Marcello H

unread,
Jul 31, 2023, 3:27:27 AM7/31/23
to golang-nuts
I think the current error handling is just fine.
For the extra typing, they invented keyboard snippets and such.

But for this proposal, I would like to see how a return with multiple values would look to get a better understanding.
```
// translate this in the proposed solution?
func myFirstFunction() (string, err) {
   result, err := myFunction()
   if err != nill {
       return rest, err
   }
}
```

Op maandag 31 juli 2023 om 04:32:01 UTC+2 schreef DrGo:

DrGo

unread,
Jul 31, 2023, 4:41:49 AM7/31/23
to golang-nuts

func myFirstFunction() (string, err) {

   result, err := myFunction() orelse return rest, err

Mark

unread,
Jul 31, 2023, 6:10:24 AM7/31/23
to golang-nuts
Given that this proposal is to reduce boilerplate, and assuming the semantic issues could be solved, it seems to me that the 'return' is redundant (i.e., could be implicit) and that 'orelse' could be done with the existing 'else' keyword, i.e.,

```
result, err := someCall() else rest, err
```
Anyway, I really do hope the long-winded error syntax gets solved somehow!

DrGo

unread,
Jul 31, 2023, 3:18:18 PM7/31/23
to golang-nuts
Me too but I do not have high hopes

Tim Casey

unread,
Jul 31, 2023, 9:59:50 PM7/31/23
to golang-nuts


I do not think this reduces boilerplate code.  This compacts it, which is different.

I think any one-liner-return-on-err makes the language harder to debug.  It is very common breakpoints are set for exceptional cases, which tend to be surprising.  If the test and the return are on the same line then all of the line-by-line tools will break down, at least a little bit.

If you see:
    x,err := something() ; err != nil {
        handleErrorAndReturn
    }

At the very least, the template will not work for things like one time retries, if error do something else, log the error and DoSomethingAdditional().  This would be a sub scope and in golang i would expect this to have '{}' as part of the scope shift.  If this is acceptable, then you are very likely to be on a separate line in any event.  So the original looks like:

  err := io.Copy(w, r) orelse {
DoSomethingElse()
}

This means the only 'boilerplate' is 'orelse' <- '; err != nil', which seems rather expensive for this error handling.

As a slight change of subject, I find the whole discussion about 'saving' boilerplate to be well over-stated, too much work and energy (at least by me as an outside observer).  Having something which fits within the design of the language, making it a developer centric language, would seem to fight with any one-line-template approach.

tim





The test and the handle all fit on one line.   I dont think having a single line return, like perl 'next if STATEMENT' style fits within golang language goals.

--
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/b87365af-9a72-4f8d-ad0b-1ee69cc1ad35n%40googlegroups.com.

DrGo

unread,
Jul 31, 2023, 11:46:42 PM7/31/23
to golang-nuts
Thanks for the valuable feedback,
The verbosity of error handling is the number one concern for Go developers in the most recent survey. So there is a need for doing something about it.. except that there are many possibly conflicting requirements outlined by the Go team. 
The problem with the abbreviated if err!= nil in your example:
  if x,err := something() ; err != nil {
        handleErrorAndReturn
    }
is that x is local to the if scope and often that won't work forcing the more verbose:
   x,err := something()
  if err != nil {
        handleErrorAndReturn
    } 
If you have few of those as is the case in io programs; there is real impact on the readability of the code.
the oresle approach avoids that problem (the x stays in the scope of the func).

   x,err := something() orelse {
        handleErrorAndReturn
    } 
You do not think this is a significant  reduction in boilerplate? The only thing that will do a better job is a try-catch which is not acceptable to the Go team (fortunately). 

why the line-by-line would break with orelse? they are working fine with the one-liner you cited:
  if x,err := something() ; err != nil 

Why it won't work with one time retires? That is like one time retires won't work in an if-else block.

Thanks again,

Tim Casey

unread,
Aug 1, 2023, 1:20:18 AM8/1/23
to DrGo, golang-nuts

>> You do not think this is a significant  reduction in boilerplate?

I understand people having a complaint about the verbosity of error handling.  But, this follows C code error handling.  So to me, it is not all *that* bad.  
I think the measurable reduction in boilerplate code is ' ; err != nil' for ' orelse '.   And I do not believe this is worth a keyword.  Some of the context is  being used to C.  And another aspect is being used to perl and its one liners.  With perl, the debugger is not something which is close to gdb/adb or the like.  So the comparison is not about saving code.

I would suggest, if I had a bold idea (and probably dumb), to have a functional way to handle this.   Something like:

    HandleErrorChain( todoList []func() (TYPE,error), errorHandler func () TYPE, error ) (TYPE, error)

Which means, I have a list of things I want to do and I want the error handling to be all the same for all of them.  Then the *chain* of things would make sense with a single handler.  This has the expense of being kinda odd so at the very least it would need idiomatic polishing.  I also think I am too ignorant to have a real say in any of this.

>> Why it won't work with one time retires? That is like one time retires won't work in an if-else block

I think there is a bit of talking past each other here.  Or at least I dont understand.  I think orelse is too specific to be helpful.  That is, it is too precise to reduce a large swath of what might look like error handling code, because error handling code can at times be specific.  The first dumb example I came up with is a simple one-time retry, which I have put in code at times.  It is usually ugly, but effective:

    v,err := dosomething()
    if err != nil {
        v,err = dosomething()
    }
    if err != nil {
        handleError(err)
   }
   importantOperation(v)

This with the token:

    if v,err := dosomething() orelse {
        v,err = dosomething() orelse {
            handleError()
        }
    }
    importantOperation(v)

The savings is the two 'err != nil' lines.

If I take a step back, some of what I am reacting to is how much it is discussed.  It *feels*, at least to me as an ignorant person in this context, as if people were not handling errors previously.  Golang is forcing this now and the resulting lines of code look like 'too much' compared to previous lines of code.  But I think this is a direct design goal.  If the keyword vs lines of code/tokenspace is worth it, so be it.  Who am I to say.  If we are truely circling on a way to handle errors in a chain, then the chain aspect is at least as important as any one line, and up to this point has largely been ignored.

In any event, i dont mean to be argumentative or even contrarian.  Sorry if it comes across that way.

tim


robert engels

unread,
Aug 1, 2023, 1:31:47 AM8/1/23
to Brian Candler, golang-nuts
For some perspective. Go’s error handling mimics C (for the most part). They had a decade to decide how to improve the error handling when they designed C++. They came up with exceptions. Java is C++ like. They had a decade to improve error handling. They came up with exceptions + throws.

The Go designers do not want exceptions in any way shape or form, so you’re pretty much going to be stuck with what you have (all of the decent proposals are “exception like”- exceptions by another name) so learn to love it or use a different language.

--
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.

Marcello H

unread,
Aug 1, 2023, 7:36:04 AM8/1/23
to robert engels, Brian Candler, golang-nuts
If I look at the following example:
```
err := io.Copy(w, r) orelse {
DoSomethingElse()
}
```
This can be written usually as:
```
if io.Copy(w, r) != nil {
DoSomethingElse()
}
```
which is LESS boilerplate. So only for the case when there is multiple return values, there is some savings in typing.

```
result, err := makeSomeNoise() orelse {
DoSomethingElse()
}
```
vs
```
result, err := makeSomeNoise() if err != nil {
DoSomethingElse()
}
```
Ehm, this saves 16-9 = 7 keyboard entries.
If you ask me, this still reads consistent with the "normal" way we handle errors.

What happens if all of a sudden, the makeSomeNoise returns 2 errors? Then you have to undo the orelse construction, for what I understand.

The more I think of this, the more I like to type those extra keyboard entries  to have a clear look about what happens in the program.

Just my 2 cents....




Op di 1 aug 2023 om 03:31 schreef robert engels <ren...@ix.netcom.com>:
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/dRLR4hxxI8A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/60596671-D3AC-49D4-8575-F8EB3D9B6BF6%40ix.netcom.com.

Jan Mercl

unread,
Aug 1, 2023, 8:10:57 AM8/1/23
to DrGo, golang-nuts
On Tue, Aug 1, 2023 at 1:47 AM DrGo <salah....@gmail.com> wrote:

> The verbosity of error handling is the number one concern for Go developers in the most recent survey.

That says something about those developers, about their preferences,
opinions, taste etc and that it differs from what the Original
Language Designers (OLD™) preferred.

It has close to zero bits of information which preferences are the
better ones. It's a subjective category anyway.

> So there is a need for doing something about it..

And here's IMO the mistake. You may feel the need, Joe and Mary may
not. It's ok to have preferences. It's ok for preferences to be
different. It does not mean there's a need to change anything. Of
course, you can decide that following the preferences of a majority of
developers is a rational move.

I claim it a fallacy. A big one. Let me not joke about billion flies,
but the fact is - language designers are few and far between while
developers come in heaps. And let's be honest. Most developers write
horrible code, me included. Maybe you're the rare exception, congrats
then. But the majority of us are just the ordinary, average coders for
hire. There are deadlines to meet, bills to pay, project mis-managers
getting into the way etc. We have all experienced that, didn't we?

I, for one learned to pay much more attention to what language
designers do and say. Sometimes I agree, sometime I don't. But I
believe one can, in essence, ignore what the majority of developers
thinks about it. Actually, I think the majority of developers is wrong
more often than the, most of the time silent, minority.

-j

Jeremy French

unread,
Aug 1, 2023, 1:32:58 PM8/1/23
to golang-nuts
I don't think this argument holds much weight.  I understand and agree that the majority is not always correct.  But then what was the point of the developer survey, if that data is irrelevant?  Isn't the existence of the developer survey an implicit statement by the Go team that they care about what Go developers think?  There is also a very similar argument here which was central to the generics debate and was one of the major arguments in favor of implementing generics - that it would significantly help some people, and it wouldn't hurt anyone else very much.  So similarly, "I don't mind it the way it is" is not a very good argument.  

I don't speak for the Go team, but my impression is that they do care about this issue, and would like to reduce the boilerplate/verbosity of error handling if they could.  But that they have seen hundreds of different proposals (thousands if you include variations on a theme), and haven't found any that qualify for the requirements that are more important to Go's nature than just verbosity.

Victor Giordano

unread,
Aug 1, 2023, 2:53:01 PM8/1/23
to golang-nuts
I think that the original Author has made a clear point. It has no sense to denied that we often write a lot of times things like...

if (err != nil) {
    return err
}

So, I understand that some people doesn't bother about that, and that is okey. But for those that doesn't like to write something twice, I guess your proposal is a good idea and highlights a boilerplate aspect across any golang project.

Great Idea Dr Go!

DrGo

unread,
Aug 1, 2023, 5:06:25 PM8/1/23
to golang-nuts
Thanks. 
The keystroke saving is not the motivation. The aim is to reduce the code reader’s mental load. My approach allows for clearer code where the main program logic is not dwarfed by the error handling code while maintaining the explicitly of error handling and the possible error-induced interruption in program flow. It avoids creating new if scope when one is not desired and offers opportunities for further deduplication of error handling code although each error is still handled individually. Compare the following; which one would you prefer to read a lot of?

- current approach; error handling to program logic ratio: 13:5 

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

- new approach ratio 5:5
func CopyFile(src, dst string) error {
r, err := os.Open(src) orelse return fmt.Errorf("copy %s %s: %v", src, dst, err)
defer r.Close()

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

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

On Sunday, July 30, 2023 at 9:27:27 PM UTC-6 Marcello H wrote:

Stephen Illingworth

unread,
Aug 1, 2023, 5:10:30 PM8/1/23
to golang-nuts
On Tuesday, 1 August 2023 at 18:06:25 UTC+1 DrGo wrote:
Compare the following; which one would you prefer to read a lot of?

You've asked a fair question so you deserve an honest answer. Looking at the two examples, I would prefer to read the code in the first example.

DrGo

unread,
Aug 1, 2023, 5:10:45 PM8/1/23
to golang-nuts

Thanks Tim for patiently explaining your perspective. Much appreciated. 
Please see my reply to Marcelo H showing using real life code  that the outcome goes much further than replacing the err!=  nil bit. 

DrGo

unread,
Aug 1, 2023, 5:12:58 PM8/1/23
to golang-nuts
Thanks Jan
Indeed the majority can be wrong but in this case the OLD smart minority too wanted a way to improve things. This approach was in fact dictated by their requirements 

DrGo

unread,
Aug 1, 2023, 5:14:56 PM8/1/23
to golang-nuts
Fair enough. But many would prefer shorter functions if there is no loss to explicitness or clarity. 

DrGo

unread,
Aug 1, 2023, 5:17:49 PM8/1/23
to golang-nuts
>> What happens if all of a sudden, the makeSomeNoise returns 2 errors? Then you have to undo the orelse construction, for what I understand.
No as long as both errors are meant to be handled as errors. Orelse will be triggered if any or both were not nil. 
Message has been deleted

DrGo

unread,
Aug 1, 2023, 5:22:41 PM8/1/23
to golang-nuts
rue but C  has a pre processor to deal with verbosity. Go does not

DrGo

unread,
Aug 1, 2023, 5:25:05 PM8/1/23
to golang-nuts
From Russ Cox’s paper

“we would like to make error checks more lightweight, reducing the amount of Go program text dedicated to error checking. We also want to make it more convenient to write error handling, raising the likelihood that programmers will take the time to do it.

Both error checks and error handling must remain explicit, meaning visible in the program text. We do not want to repeat the pitfalls of exception handling.

Existing code must keep working and remain as valid as it is today. Any changes must interoperate with existing code.”


On Tuesday, August 1, 2023 at 2:10:57 AM UTC-6 Jan Mercl wrote:

Stephen Illingworth

unread,
Aug 1, 2023, 5:35:54 PM8/1/23
to golang-nuts
On Tuesday, 1 August 2023 at 18:14:56 UTC+1 DrGo wrote:
Fair enough. But many would prefer shorter functions if there is no loss to explicitness or clarity.

I don't think putting the assignment and return statement on the same line is very clear. I would prefer
the compiler to enforce something like the following:

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

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

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

What do you intend to happen to the error instances after the orelse block? Are they valid outside the orelse block?


Luke Crook

unread,
Aug 1, 2023, 7:04:33 PM8/1/23
to golang-nuts
And of course I forgot the "if" at the beginning of all those conditional. *sigh*

Luke Crook

unread,
Aug 1, 2023, 7:04:34 PM8/1/23
to DrGo, golang-nuts
On Tue, Aug 1, 2023 at 10:18 AM DrGo <salah....@gmail.com> wrote:
>> What happens if all of a sudden, the makeSomeNoise returns 2 errors? Then you have to undo the orelse construction, for what I understand.
No as long as both errors are meant to be handled as errors. Orelse will be triggered if any or both were not nil. 

Some of my thoughts.I can see a developer (using myself as an example) assuming this will work in the generalized case of any non-nil return value (not just of type error). "orelse" doesn't really convey the intent of restricting this conditional only to the handling of errors.

What would happen if the error is ignored?

```
count, _ := io.Copy(w, r) orelse {
    DoSomethingElse()
}
```

And does anyone besides me write code like this?

```
result, err := makeSomeNoise(); err != nil && result > 0 {
    DoSomethingElse()
}
```

Would this become,

```
result, err := makeSomeNoise() orelse if result > 0 {
    DoSomethingElse()
}
```

or,

```
result, err := makeSomeNoise() orelse {
   if result > 0 {
      DoSomethingElse()
   }
}
```
 


Victor Giordano

unread,
Aug 1, 2023, 8:59:07 PM8/1/23
to Luke Crook, golang-nuts
Yeah.. I mean, the "idiom" `err != nil return` err is something of the language. I complain about the boilerplate that idiom produces and that is fact fact (no one can deny it).

You know, your approach implies making the language a little more complicated as new ways to deal with errors appear. I do understand that some folks provide some push back on the idea simply because there is nothing wrong with the language right now regarding error handling. 

As I see things, the language was simple in their origins, but from time to time they complicated a little more some things, for example "what about generics?"  (are they really necessary?, I mean... I think using interfaces provides all the genericity you may need). So I guess there is room to make some changes and make the language easier. I would say that both ways of handling errors are valid, the most important is to be as simple as possible so you ensure that other people understand it. Like Generics, you don't have to use them. So I would praise it for adding another way, less repetitive.

Also like to see how people react and what their opinions are. So far what I read is just personal taste.


El mar, 1 ago 2023 a las 16:04, 'Luke Crook' via golang-nuts (<golan...@googlegroups.com>) escribió:
And of course I forgot the "if" at the beginning of all those conditional. *sigh*

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/dRLR4hxxI8A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.


--
V

DrGo

unread,
Aug 1, 2023, 9:24:53 PM8/1/23
to golang-nuts
Good points
The compiler right now forces if and else to be on a separate line but only because Go does not use parentheses around if conditions. But the formatted inline single line funcs. So there is precedence for both policies. 

>> What do you intend to happen to the error instances after the orelse block? Are they valid outside the orelse block?

yes. Because the associated values are often needed in subsequent code as is the case with the w and r variables in the above code

DrGo

unread,
Aug 1, 2023, 9:33:10 PM8/1/23
to golang-nuts
Thanks Luke. Your examples were clear and you do have a good point about orelse not conveying that only the errors variables are looked at. Luckily in Go errors are often an indication that the associated values returned by the func are null so semantically orelse remains indicative of the situation where bad things are happening. 

For your second question this would be my preferred solution (your second option):

DrGo

unread,
Aug 1, 2023, 9:41:38 PM8/1/23
to golang-nuts
Yes. Go is no longer the simple language it was. I suspect because of internal pressures within Google as evidenced by multiple innovations that seem to come from nowhere eg dir embedding and associated fs package that duplicated perfectly good ways of doing things. The module system while useful is quite complex. Generics and all the associated packages inflated the mental burden of learning and reading Go code significantly. And having the go 1 compatibility guarantee means that old stuff remains valid code and must be learned too. 

Brian Candler

unread,
Aug 2, 2023, 6:54:20 AM8/2/23
to golang-nuts
FWIW, I'm in the "I like how it is now better than any other proposal so far" camp; I think this happens as you get used to the Go way. Go is Go.

The only thing I would consider is making *interface* types (only) implicitly usable in a boolean context, e.g.

if err { ... }

However, I suppose people would ask "why not pointers? why not channels?" etc.  I'm not suggesting it should become like Python where every non-zero value is treated as "true".  Interface values are special, and there's very little you can do with a nil interface (whereas for example, a nil pointer can still have methods called on it).  But this does add a special case, and Go already has its share of surprises you have to learn.

DrGo

unread,
Aug 2, 2023, 4:11:01 PM8/2/23
to golang-nuts
Fair enough … I understand that people have different styles 

TheDiveO

unread,
Aug 2, 2023, 7:27:23 PM8/2/23
to golang-nuts

Ben Hoyt's blog post Scripting with Go: a 400-line Git client that... mentions error handling from a perspective that made me clear for the first time why I'm always in two minds when it comes to Go's error handling:
  1. the perspective of a prod-code package writer that will be used in larger contexts: "[Go's error handling is] simple and explicit [...] It's not a big deal when writing production code, because then you want more control over error handling anyway -- nicely-wrapped errors, or human-readable messages [...]"
  2. from a "script" developer perspective, where "all the error handling you need is to show a message, print a stack trace, and exit the program".
There might be more angles to it, but Ben's sentiment rings a bell with my own "customer" experience.

So we can at least expect to have two "camps of judges" when it comes to error handling improvement proposals. Some of the judges might be acutely aware of the at least two different angles, but some of the comments not least in this thread ("language designers" as kind of gods who must ignore their customers, seriously?) seem to indicate that this isn't always the case. Not least, I also fall into the less desirable category of the ignoramus.

So, maybe we should in the future always ask when it comes to a proposal: which of the two perspectives does the proposal tackle? I'm under the assumption that it might have been #2 in most of the proposals. The often vivid negative responses should then be classified as belonging to #1 and/or #2. If the proposal is about #2, then #1 proponents don't contribute, but simply make life hard for the #2 customers of the Go language. Same for the opposite combination.

TheDiveO

unread,
Aug 2, 2023, 7:30:00 PM8/2/23
to golang-nuts
Sorry to f'up on myself, but I would like to add regarding #1: at least my personal impression is that for #1 it looks very difficult to improve this in any meaningful way. It looks to me as if #2 is actually where the improvements would bear large fruit as it makes Go more welcoming and productive to those types of devs and/or types of Go modules, that is, applications.

Wojciech S. Czarnecki

unread,
Aug 2, 2023, 8:58:42 PM8/2/23
to golan...@googlegroups.com
Dnia 2023-07-29, o godz. 22:57:15
DrGo <salah....@gmail.com> napisał(a):

> This involves introducing a new keyword "orelse" that is a syntactic sugar for an "if err!=nil" block.

You can implement it as a snippet under any editor in use today.

If your goal is to personally have it in just one line, you can turn off gofmt and write `; if err !=nil {; return err }`

I'd like to point out that considering

> It is an error to not return an error from an orelse block.

The `return err` in examples still is a boilerplate, so the overall savings are just four utf-8 bytes, ie. {\n}\n.

I understand sentiment, 'cos there is a huge camp of devs claiming that any error handling proper is one that is done by something/someone up the callstack, even future-me — for this camp idiomatic Go can be annoying.

But note that for the most pleasant coder experience the `orelse return err` is way too much of text to write.

I would better stay with ^^ digraph instead. Lets name it a 'bat operator'.

func CopyFile(src, dst string) error {
r, err := os.Open(src) ^^
defer r.Close()
w, err := os.Create(dst) ^^
defer w.Close()
err = io.Copy(w, r) ^^
err = w.Close() ^^
}

> It also works well with named returns. e.g.,

func returnsObjorErro() (obj Obj, err error) {
obj, err := createObj() ^^ //returns nil and err
}

> otherwise ^^ is like "else" so e.g., it can be followed by a block if
> additional cleanup or error formatting etc is needed before returning, eg

w, err := os.Create(dst) ^^ {
....
return err
}

P.S — bat operator is easily implementable in an IDE with onFileOpen/onFileWrite hooks.
Its the piping of content to tools (VSCode and GoLand) that bars me from personally using it.

Hope this helps,

--
Wojciech S. Czarnecki
<< ^oo^ >> OHIR-RIPE

DrGo

unread,
Aug 3, 2023, 11:12:17 PM8/3/23
to golang-nuts
Great point! 
Gophers like any human tribes can become victims of absolutest thinking. In the gophers' case, tenets like "there should be only way of doing things" while admirable and justified most of the time can inhibit discussions about possible improvements. There are times where it is perfectly acceptable to have two ways to do something; and I argue that something as important and fundemental as error handling is one of those things. Your explanation above demonstrates the case.

DrGo

unread,
Aug 3, 2023, 11:13:40 PM8/3/23
to golang-nuts
Fully agree.. I think my proposal is targeted primarily at that group although recognizing the strength and ability of group #1 to block any discussion

DrGo

unread,
Aug 3, 2023, 11:18:04 PM8/3/23
to golang-nuts
I think the proposal goes beyond saving few keystrokes.. as demonstrated in several of my replies above. The issue is readability and avoiding creating new scope with the current shortcut approach if x, err:=fn();  err!=nil.

Miguel Angel Rivera Notararigo

unread,
Aug 4, 2023, 3:07:52 AM8/4/23
to DrGo, golang-nuts
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)
}
}

I think it is a bad example, how do you know where CopyFile failed?

The "copy ..." part shouldn't be in there, you should add valuable context to your errors, if CopyFile fails, the caller already knows it was a copy error because the function has a big "Copy" on his name right? you should do this instead:

func CopyFile(dst, src string) error {
  r, errOS := os.Open(src) // Avoid shadowing errors, don't use err
  if errOS != nil {
    return fmt.Errorf("cannot open source: %v", errOS)
  }

  defer r.Close()

  w, errCD := os.Create(dst)
  if errCD != nil {
    return fmt.Errorf("cannot create destination: %v", errCD)
  }

  defer w.Close()

  if _, err := io.Copy(w, r); err != nil { // Local scope error, so err is fine
    os.Remove(dst)
    return fmt.Errorf("cannot copy data from source: %v", err)

  }

  if err := w.Close(); err != nil {
    os.Remove(dst)
    return fmt.Errorf("cannot close destination", err)
  }
}

// Caller should do this.
if err := CopyFile("dst.txt", "src.txt"); err != nil {
  // Here is where you should add 'copy' to the error message.
  return fmt.Errorf("cannot copy '%s' to '%s': %v", src, dst, err)
}

People complaining about Go's error handling regularly don't handle errors, they just throw them like exceptions.

If you really hate Go's error handling, just use:

func catch(err error) {
  if err != nil {
    panic(err)
  }

  // And use recover somewhere
}

Which is a bad practice, but at least we (the people who like how Go handle errors) can still handle our errors without any language change.

--
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/36110a8d-a26f-48be-83fd-73af755e88f4n%40googlegroups.com.

DrGo

unread,
Aug 4, 2023, 3:45:18 AM8/4/23
to golang-nuts
@Miguel Angel Rivera Notararigo

Thanks for taking the time to write... 

In my proposal, people are free to add as much context as they want... but as a demonstration, I am using the example from 
Ross Cox's paper on error handling that is used by all error handling proposals to show case their approach. I do not think Ross will 
take your criticism personally :) I on the other hand take exception to your generalization re people who complain about error handling in Go.
I am sure you did not make that claim without having some sort of solid research to support it. But, Go designers themselves admit that this is an issue and have written
tons on it.

In one or two replies above we were discussing how error handling opinions can become religions each with its own priests who think they are the only saved faction, and that their rituals are the only right approach for all situations.

Best wishes,


Miguel Angel Rivera Notararigo

unread,
Aug 4, 2023, 12:47:37 PM8/4/23
to DrGo, golang-nuts


On Thu, Aug 3, 2023, 23:45 DrGo <salah....@gmail.com> wrote:
@Miguel Angel Rivera Notararigo

Thanks for taking the time to write... 

In my proposal, people are free to add as much context as they want...

Yeah I know, I like your proposal, it is just how they handle errors in the V programming language, although they use the keyword "or" and have Options/Results. 

but as a demonstration, I am using the example from 
Ross Cox's paper on error handling that is used by all error handling proposals to show case their approach. I do not think Ross will 
take your criticism personally :)

I know, but just because he wrote that, doesn't mean it is the perfect example. If I find myself writing code like that, I would probably hate Go's error handling too, but that might be said for any other feature of the language if I write some bad looking examples.

I don't see how he could take my opinion personally, I didn't mean to be rude, I just said it is a bad example. I am not a native speaker, so my apologies if I wrote something rude.

I on the other hand take exception to your generalization re people who complain about error handling in Go.
I am sure you did not make that claim without having some sort of solid research to support it. But, Go designers themselves admit that this is an issue and have written
tons on it.

You don't need a research or statistics if the complain is: "I don't want to write 'if err != nil { return err }' every single time".

It means they use "if err := w.Close(); err != nil { return err }" for throwing the error, right? It is literally what they said or am I assuming?

This same people will complain about your proposal, "I don't want to write 'orelse return err' every single time", and then we will add 'w.Close()!' or 'try w.Close()' to the language, and then we will be very happy with our Go++ wich has 3 ways of doing the same thing.


In one or two replies above we were discussing how error handling opinions can become religions each with its own priests who think they are the only saved faction, and that their rituals are the only right approach for all situations.

I know, as I said, I like your proposal, but one of Go best features is the lack of new features. I am arguing against my own taste, because I like 'w.Close() or return fmt.Errorf("cannot close destination: %v", err)', but I like more the simplicity of Go.

Victor Giordano

unread,
Aug 4, 2023, 12:59:15 PM8/4/23
to Miguel Angel Rivera Notararigo, DrGo, golang-nuts
As far as I see things there is always room for changes... but changes doesn't come without some resistance.. That is natural...

>  Go best features is the lack of new features.

What about generics? That was a major change... It was really necessary or not is another topic.

You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/dRLR4hxxI8A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAF9DLCkTKJz8tpOwFQzV%3DoXe6R6aS-0ssnXdwDraDS112gnQ0w%40mail.gmail.com.


--
V

Miguel Angel Rivera Notararigo

unread,
Aug 4, 2023, 1:16:34 PM8/4/23
to Victor Giordano, DrGo, golang-nuts
It is not just resistance to change, it is about not adding new features that add more complexity than value. I am pretty sure people will complain about Go's error handling even if we use "orelse return err".

Generics add a lot of value, it shows the Go team is open to changes. But imagine they add any feature people ask, C++ would be simpler than Go.

As I said, I like the proposal, but I have learned that some times, I have to accept things are not always as I want them to be.

Victor Giordano

unread,
Aug 4, 2023, 1:27:10 PM8/4/23
to Miguel Angel Rivera Notararigo, DrGo, golang-nuts
> Generics add a lot of value

From a personal point of view with the interfaces I got all the genericity I needed to model solutions, and generics per se doesn't provide a new approach to find solutions.
I Mean, you can solve the same old problems with or without generics...  Generics provides another way of writing code, just as this proposal to deal with errors. This is my view on this matter.


--
V

Miguel Angel Rivera Notararigo

unread,
Aug 4, 2023, 1:38:23 PM8/4/23
to Victor Giordano, DrGo, golang-nuts
I understand, but there is a little difference, you can find code out there improving performance thanks to generics, I am not sure if we can get any performance improvement by using "orelse return err".

Victor Giordano

unread,
Aug 4, 2023, 2:25:33 PM8/4/23
to Miguel Angel Rivera Notararigo, DrGo, golang-nuts
Ok, that is a point to have in mind.

El vie, 4 ago 2023 a las 10:37, Miguel Angel Rivera Notararigo (<ntr...@gmail.com>) escribió:
I understand, but there is a little difference, you can find code out there improving performance thanks to generics, I am not sure if we can get any performance improvement by using "orelse return err".


--
V

DrGo

unread,
Aug 4, 2023, 4:50:27 PM8/4/23
to golang-nuts
Thanks Miguel and I am not offended.. It sounds you like the proposal and your comments were not about the proposal per se but about the cultural issues surrounding change and fear of unnecessary growth; here we agree too.

Harri L

unread,
Aug 4, 2023, 5:57:16 PM8/4/23
to golang-nuts

Yes, we can handle Go errors without language changes; that’s true. But how we should annotate errors? The presented version of CopyFile leads to following 'stuttering':

cannot copy '/tmpfs/play' to './tmp33/not-exist.txt': cannot create destination: open ./tmp33/not-exist.txt: no such file or directory

You can get compact but informative error messages just by following K&D annotation rules:

  1. Annotate the function you are implementing
  2. Let the functions that you are calling do their own job
copy file: open ./tmp33/not-exist.txt: no such file or directory

All of this is just basic error propagation, which can be automated to avoid annoying human errors. You can see three different error annotation scenarios of the FileCopy implementations in this playground.

And the function itself is compact as well — and fully automated (tip: change the name of the function):

func copyFile(src, dst string) (err error) { defer err2.Handle(&err) r := try.To1(os.Open(src)) defer r.Close() w := try.To1(os.Create(dst)) defer err2.Handle(&err, func() { os.Remove(dst) }) defer w.Close() try.To1(io.Copy(w, r)) return nil }

Miguel Angel Rivera Notararigo

unread,
Aug 4, 2023, 7:38:15 PM8/4/23
to Harri L, golang-nuts


On Fri, Aug 4, 2023, 13:57 Harri L <har...@gmail.com> wrote:

Yes, we can handle Go errors without language changes; that’s true. But how we should annotate errors? The presented version of CopyFile leads to following 'stuttering':

cannot copy '/tmpfs/play' to './tmp33/not-exist.txt': cannot create destination: open ./tmp33/not-exist.txt: no such file or directory


You will notice there is repeated information, and it is because os.Create adds meaningless context to the error message ("open ./tmp33/not-exist.txt"), file path is owned by caller, and caller should handle its problems. It should be:

cannot copy '/tmpfs/play' to './tmp33/not-exist.txt': cannot create destination: no such file or directory

I thinks its pretty easy to read. But if you prefer sed "s/: /\n\n/g":

cannot copy '/tmpfs/play' to './tmp33/not-exist.txt'

cannot create destination

no such file or directory

You don't even need a stack trace to know where it failed.

If you use pre-declared errors, you can even go further and do some handling programmatically, like "errros.Is(err, Retry)" for retrying without any complicated dependency.


You can get compact but informative error messages just by following K&D annotation rules:

  1. Annotate the function you are implementing
  2. Let the functions that you are calling do their own job
copy file: open ./tmp33/not-exist.txt: no such file or directory


For me (personal opinion), that message is not easy to understand.

copy file
Is this a log message telling me it is copying some file? or is it an error message?
open ./tmp33/not-exist.txt
Oh, cool, it was a log message, it is opening a file now, keep it going buddy, you can copy that file :)
no such file or directory
Well, it is a failure, I guess. But now I don't know where it failed, is that source or destination?

With previous message:

cannot copy '/tmpfs/play' to './tmp33/not-exist.txt'
No! why you do this to me?
cannot create destination
But WHY!? I have been so nice to you 'not-exist.txt'
no such file or directory
Oh, you don't even exist haha sorry, drama moment

(We would never get that error because os.Create creates the file, but I am using your example)

Using specific annotation is a different topic, but the primary goal of this error messages is being hints for developers. For end users you will be probably using translations or anything else.

All of this is just basic error propagation, which can be automated to avoid annoying human errors. You can see three different error annotation scenarios of the FileCopy implementations in this playground.

And the function itself is compact as well — and fully automated (tip: change the name of the function):

func copyFile(src, dst string) (err error) { defer err2.Handle(&err) r := try.To1(os.Open(src)) defer r.Close() w := try.To1(os.Create(dst)) defer err2.Handle(&err, func() { os.Remove(dst) }) defer w.Close() try.To1(io.Copy(w, r)) return nil }

Not sure, but this looks like try-catch exceptions in Go.
Reply all
Reply to author
Forward
0 new messages