Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Coffee Mugshot
Jun 26, 2010

by Lowtax

TooMuchAbstraction posted:

I fail to see how this is any worse (or better) than the Go equivalent:
code:
foo, _ := thingThatCanFail(); // second return val is an error, but who cares?
The thing that bugs me most about Go's error handling is that you don't get stack traces out of it. Like, the zero-effort thing to do with exceptions is just let them propagate up to a top-level exception handler, which then says "hey, this function buried ten levels deep in your program tried to use a dead network connection on line 128 of db.java, you dumbass." Whereas in Go you have to do stuff like this for every single function call:
code:
if err := doAThing(); err != nil {
  return fmt.Errorf("couldn't doAThing: %s", err)
}
And then you get to decipher the corresponding top-level message: "couldn't process request: couldn't retrieve customer details: couldn't get customer ID: couldn't doAThing: database connection went away". And if you don't write a little description for each error, and instead just return the bare error, then you get errors like "couldn't process request: database connection went away", which is absolutely inscrutable.

Languages should make it easy to do the right thing. But far more important than making it easy to do the right thing is making it harder to do the wrong thing. Go's mistake here is that the mechanism it came up with becomes useless when misused, and misusing it is what lazy programmers are going to do because it saves them a little typing/thinking in the short term.

This is a pretty big problem in Go, also see: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully. Being able to annotate errors is very useful to make them descriptive and human-readable, but programmatically dealing with an error, you want to be able to dig down to what really caused it to initially happen, perhaps in the form of a stack trace. The error interface in general has a lot of flaws as the main mechanism for error handling. It provides a good basic method set and leaves the rest to implementations of error to handle these details. I thought we might have gotten a proposal in for Go1.9 about this, but it didn't happen.

Adbot
ADBOT LOVES YOU

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

Coffee Mugshot posted:

I don't why people refer to Rob Pike so much when he hasn't worked on the project for like a year at this point.

Are you feigning confusion here or are you really in that much of a bubble? Rob Pike was the public face of Go for most of the language's public existence.

comedyblissoption
Mar 15, 2006

quote:

I was working at Google when Go started to come out, and this was exactly the response I got from Rob Pike when I asked about error handling.
Basically, I felt that only doing error handling through return codes was dangerous and bulky.
It's dangerous because there's no way to enforce that callers check that return code, and worse, because if I add a return code to an existing function, I have no way to make sure that existing callers check that return code.
It's bulky because, in a mature program, many subroutine calls have the possibility of errors, so almost every subroutine call needs a manual error check, which then propagates up to all callers too. I feel bulky, repetitive code is bad because both the programmer and the reviewers get tired when reading it and start to skim the details.
Pike's response was, "Don't do that." Basically, developers should always check error codes, and they're just dumb if they don't; and I should never add an error code to a function that didn't have one before. And regarding bulky, well, that wasn't even considered an issue by Pike.

JewKiller 3000
Nov 28, 2006

by Lowtax

KernelSlanders posted:

What "unequivocally better solution" are you referring to?

Please don't say exceptions because those are also horrible and go recognized the problem, but like everything else in that language the attempted fix fell way short of its potential.

pattern matching over algebraic data types

comedyblissoption
Mar 15, 2006

One imperative alternative to go's error handling that doesn't use exceptions is the way Rust does it with sum types. In Rust, errors are expressed as Result<TSuccess, TError>. A value must either be a success or a failure. These states are mutually exclusive. You are forced by the language to handle both cases. You cannot forget the error situation if you want access to the success value. A return value that may have an error has a different type than a return value that cannot have an error. If you change a function return signature to return an error when it did not before, the compiler will let you know all of the call sites that are now broken as a result. You cannot have garbage non-sense values that you are by convention supposed to ignore like in Go.

Rust also has syntactic sugar for the common case of just propagating the error up the call stack.
https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html
code:
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}
Every ? in that example will propagate an error to the caller. If there was no error, then the expression will result in the success value. Handling errors as values instead of exceptions does not require golang-ish boilerplate.

comedyblissoption
Mar 15, 2006

Rust does also have an exception-like mechanism called panics, but these are intended for non-recoverable errors that are not intended to be handled by a caller (divide by zero, out of memory, accessing an array out of bounds, etc.). Usually these errors signal non-recoverable bugs in the program.

hyphz
Aug 5, 2003

Number 1 Nerd Tear Farmer 2022.

Keep it up, champ.

Also you're a skeleton warrior now. Kree.
Unlockable Ben
Obviously we need to bring back restarts ;)

TheBlackVegetable
Oct 29, 2006

comedyblissoption posted:

One imperative alternative to go's error handling that doesn't use exceptions is the way Rust does it with sum types. In Rust, errors are expressed as Result<TSuccess, TError>. A value must either be a success or a failure. These states are mutually exclusive. You are forced by the language to handle both cases. You cannot forget the error situation if you want access to the success value. A return value that may have an error has a different type than a return value that cannot have an error. If you change a function return signature to return an error when it did not before, the compiler will let you know all of the call sites that are now broken as a result. You cannot have garbage non-sense values that you are by convention supposed to ignore like in Go.

Rust also has syntactic sugar for the common case of just propagating the error up the call stack.
https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html
code:
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}
Every ? in that example will propagate an error to the caller. If there was no error, then the expression will result in the success value. Handling errors as values instead of exceptions does not require golang-ish boilerplate.

F# encourages this method too; it really does give you a feeling of confidence in the code that you don't get with exceptions - you can see explicitly in your code exactly where things can go wrong and how they have been handled, as opposed to thrown exceptions that might have occurred somewhere nested N functions deep. I haven't really paid attention to Rust, but this is enough to make it my next language to learn - at least over Go anyway.

TheBlackVegetable fucked around with this message at 20:48 on Sep 21, 2017

Pollyanna
Mar 5, 2005

Milk's on them.



See? Clearly if you find Go difficult or cumbersome you're just a fuckin pussy :smug:

Coffee Mugshot
Jun 26, 2010

by Lowtax

Plorkyeran posted:

Are you feigning confusion here or are you really in that much of a bubble? Rob Pike was the public face of Go for most of the language's public existence.

I'm feigning confusion because someone referenced Rob Pike getting his head out of his rear end for something something generics implementation in Go2 and he has nothing to do with that except for text snippets on the internet that have been around for a decade. It's really weird to engage with that, honestly. If you dislike Go because Rob Pike was an rear end in a top hat, it's unlikely you've had enough time to actually consider reasonable applications of the language.

Coffee Mugshot
Jun 26, 2010

by Lowtax

JewKiller 3000 posted:

pattern matching over algebraic data types

I hope you write code that's less obtuse and snarky that your posts. Spell it out for us that are too daft to understand how simple it is.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.

Coffee Mugshot posted:

I hope you write code that’s less obtuse and snarky that your posts. Spell it out for us that are too daft to understand how simple it is.

comedyblissoption posted:

One imperative alternative to go’s error handling that doesn’t use exceptions is the way Rust does it with sum types. In Rust, errors are expressed as Result<TSuccess, TError>. A value must either be a success or a failure. These states are mutually exclusive. You are forced by the language to handle both cases. You cannot forget the error situation if you want access to the success value. A return value that may have an error has a different type than a return value that cannot have an error. If you change a function return signature to return an error when it did not before, the compiler will let you know all of the call sites that are now broken as a result. You cannot have garbage non-sense values that you are by convention supposed to ignore like in Go.

Rust also has syntactic sugar for the common case of just propagating the error up the call stack.
https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html
code:
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open(“hello.txt”)?.read_to_string(&mut s)?;

    Ok(s)
}
Every ? in that example will propagate an error to the caller. If there was no error, then the expression will result in the success value. Handling errors as values instead of exceptions does not require golang-ish boilerplate.

Coffee Mugshot
Jun 26, 2010

by Lowtax
I see, you're just talking about variant types in general being used for errors. Probably wouldn't work well with Go interfaces if one of the variants is an interface itself. How does rust handle this?

comedyblissoption
Mar 15, 2006

Coffee Mugshot posted:

I see, you're just talking about variant types in general being used for errors. Probably wouldn't work well with Go interfaces if one of the variants is an interface itself. How does rust handle this?
Rust handles this situation perfectly fine. Instead of returning the type:
code:
Result<String, io::Error>
where io::Error is a specific concrete type, you would do:
code:
Result<String, Box<std::error::Error>>
where std::error::Error is a specific trait in the stdlib. Rust uses traits to get programming functionality similar to interfaces in other languages. Different concrete error types (e.g. io::Error) can implement this trait. The trait is "open for extension" and is not limited to a fixed number of closed types like a sum algebraic data type.

The Box just indicates that you have a "smart pointer" to the trait. The Box is a smart pointer to some data. The Box is necessary because of the way Rust requires you to be explicit about the size of values on the stack. Since a trait's concrete type could be of variable size, the data of the actual type implementing the trait will live on the heap. You need to use Box to be explicit about this in Rust.

Asymmetrikon
Oct 30, 2009

I believe you're a big dork!

Coffee Mugshot posted:

If you dislike Go because Rob Pike was an rear end in a top hat, it's unlikely you've had enough time to actually consider reasonable applications of the language.

Disliking a language because one of its loudest designers is a shithead is actually entirely reasonable.

lifg
Dec 4, 2000
<this tag left blank>
Muldoon

Asymmetrikon posted:

Disliking a language because one of its loudest designers is a shithead is actually entirely reasonable.

It's not reasonable, but it is very satisfying. For me, Rails.

vOv
Feb 8, 2014

I'm a pretty big fan of explicit error values like in Rust but the one problem with them, like was mentioned upthread, is stack traces. I know that the error-chain crate which does a lot of macro hackery to make error management easy also generates backtraces, which seems nice, but I've never used it in anger so I have no clue how well it works in practice.

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

vOv posted:

I'm a pretty big fan of explicit error values like in Rust but the one problem with them, like was mentioned upthread, is stack traces. I know that the error-chain crate which does a lot of macro hackery to make error management easy also generates backtraces, which seems nice, but I've never used it in anger so I have no clue how well it works in practice.

Can you manually create a StackTrace object in Rust? In F#, Result<'success, exn> or Result<'success, SomeExceptionSubClass> is a somewhat common pattern when the error is exogenous (like I/O or data errors, as opposed to business logic errors like WidgetNotFound) and so you aren't going to define your own specific error type, because you would have to start by catching an exception anyway. The Exception object then includes its own stack trace.

vOv
Feb 8, 2014

I haven't looked too deeply into how error-chain implements backtraces so I don't really know, sorry.

Beef
Jul 26, 2004
Manually-propagating errors codes on almost every function is definitely a sign that the language isn't supporting something that it should (and that the designers are too comfortable with C). It is essentually forcing the programmer to accept two return values every function invocation: success, failure (oh hey, SICP amb-eval)
Reduction ad absurdum: you do not ask programmers to manually prepare and clean up the stack for a function call.

Spatial
Nov 15, 2007

This week on hardware horrors: the creeping realisation that if we can't use the product we made, customers might not be able to either.

No six figure fuckups this time because it's seven figures.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
I come from a numerical computing background so my bread and butter are C and Fortran (and lately C++ and Python) so I have no idea what error handling is and in fact I'd rather not have my compiler or interpreter handle errors for me. I want my compiler to produce code that is true to what I wrote with exceptions to automatic optimizations, which I will request iff I want, and no funny business going on under the hood. If there are errors it should either segfault when appropriate or return error codes and exit instead of making any assumptions about what it thinks I intended.

Anyway that's my story.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
My experience with C is that an error is just as likely to silently return bad data without you knowing that anything went wrong until much later, if at all. That seems like a way worse outcome than ... basically anything else.

For what it's worth, the default behaviour with exceptions is literally what you describe - "an error happened, let's exit with enough information that the programmer can figure out what happened". If you want any other behaviour, you need to explicitly ask for it.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
[quote="“Jabor”" post="“476647848”"]
My experience with C is that an error is just as likely to silently return bad data without you knowing that anything went wrong until much later, if at all. That seems like a way worse outcome than ... basically anything else.
[/quote]

So you create some verification and validation test cases with well defined inputs and known, exact, outputs. It's not rocket science, but it could be if that's what you're simulating.

Munkeymon
Aug 14, 2003

Motherfucker's got an
armor-piercing crowbar! Rigoddamndicu𝜆ous.



Error handling is what you do when you want your program to do something other than fail with an obscure error when there's a problem OP

It's for people who write software that they expect other people use out in the world

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

Boris Galerkin posted:

So you create some verification and validation test cases with well defined inputs and known, exact, outputs. It's not rocket science, but it could be if that's what you're simulating.

Are you one of those dudes who was writing code for fMRI machines?

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
[quote="“Jabor”" post="“476648054”"]
Are you one of those dudes who was writing code for fMRI machines?
[/quote]

Nope, I write code to solve the equation Ax = b. It's as thrilling as it sounds :v:

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Boris Galerkin posted:

I come from a numerical computing background so my bread and butter are C and Fortran (and lately C++ and Python) so I have no idea what error handling is and in fact I'd rather not have my compiler or interpreter handle errors for me. I want my compiler to produce code that is true to what I wrote with exceptions to automatic optimizations, which I will request iff I want, and no funny business going on under the hood. If there are errors it should either segfault when appropriate or return error codes and exit instead of making any assumptions about what it thinks I intended.

Anyway that's my story.

Sometimes people get overly attached to what they're used to.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
[quote="“Thermopyle”" post="“476648287”"]
Sometimes people get overly attached to what they’re used to.
[/quote]

You haven't lived until you've used an obscure library written in FORTRAN77 that's been "forked" three times with hacked together "it works don't touch this I'm serious" features.

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Boris Galerkin posted:

You haven't lived until you've used an obscure library written in FORTRAN77 that's been "forked" three times with hacked together "it works don't touch this I'm serious" features.

Ahh, the good ole days.


(haha, who am i kidding, thats happening right now)

Dr. Stab
Sep 12, 2010
👨🏻‍⚕️🩺🔪🙀😱🙀

Boris Galerkin posted:

I come from a numerical computing background so my bread and butter are C and Fortran (and lately C++ and Python) so I have no idea what error handling is and in fact I'd rather not have my compiler or interpreter handle errors for me. I want my compiler to produce code that is true to what I wrote with exceptions to automatic optimizations, which I will request iff I want, and no funny business going on under the hood. If there are errors it should either segfault when appropriate or return error codes and exit instead of making any assumptions about what it thinks I intended.

Anyway that's my story.

What language are you talking about that automatically tries to resolve errors on its own?

Pie Colony
Dec 8, 2006
I AM SUCH A FUCKUP THAT I CAN'T EVEN POST IN AN E/N THREAD I STARTED
https://github.com/munificent/vigil

Beef
Jul 26, 2004

Boris Galerkin posted:

I come from a numerical computing background so my bread and butter are C and Fortran (and lately C++ and Python) so I have no idea what error handling is and in fact I'd rather not have my compiler or interpreter handle errors for me. I want my compiler to produce code that is true to what I wrote with exceptions to automatic optimizations, which I will request iff I want, and no funny business going on under the hood. If there are errors it should either segfault when appropriate or return error codes and exit instead of making any assumptions about what it thinks I intended.

Anyway that's my story.

Aaah yes, like in HPC, where segfault is a valid way to handle errors.

No kidding, I had a graph-construction library segfault as a way to handle me passing it the wrong node index.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
Ok now I'm being serious because I don't understand/know: what would you have it rather do? If the node index was out of range then it should attempt to read memory it can't read and crash, that's how I see it. The other options would be "assume user meant to access last index" or "ignore this error and keep going." Both seem like terrible options to me.

e: If you say "it should just note the error and exit it gracefully" then well I just don't see the point/how that's different than just segfaulting. It's not like I can do anything about it other than fixing the problem and recompiling/rerunning.

Boris Galerkin fucked around with this message at 15:51 on Sep 22, 2017

Jeb Bush 2012
Apr 4, 2007

A mathematician, like a painter or poet, is a maker of patterns. If his patterns are more permanent than theirs, it is because they are made with ideas.

Boris Galerkin posted:

I come from a numerical computing background so my bread and butter are C and Fortran (and lately C++ and Python) so I have no idea what error handling is and in fact I'd rather not have my compiler or interpreter handle errors for me. I want my compiler to produce code that is true to what I wrote with exceptions to automatic optimizations, which I will request iff I want, and no funny business going on under the hood. If there are errors it should either segfault when appropriate or return error codes and exit instead of making any assumptions about what it thinks I intended.

Anyway that's my story.

apparently not, because "making assumptions about what it thinks you intended" is not what people mean by "error handling"

good error handling stuff does the same things as "segfaulting when appropriate" or "returning error codes", but in ways that are less prone to, well, error (relying on segfaults for error handling is particularly nuts because segfaults are non-deterministic and any time your program could segfault it could do something horrible and undetectable)

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!

Jeb Bush 2012 posted:

apparently not, because "making assumptions about what it thinks you intended" is not what people mean by "error handling"

good error handling stuff does the same things as "segfaulting when appropriate" or "returning error codes", but in ways that are less prone to, well, error (relying on segfaults for error handling is particularly nuts because segfaults are non-deterministic and any time your program could segfault it could do something horrible and undetectable)

Well in my opinion sqrt(2) is an error because the argument is an int and the return is a real. I'd prefer it give me an error outright, rather than assume I meant to do sqrt(2.0), even though I most likely did. I guess stuff like that is mostly what I mean by languages that automatically handle errors for you cause it's an error to me.

quiggy
Aug 7, 2010

[in Russian] Oof.


Boris Galerkin posted:

Well in my opinion sqrt(2) is an error because the argument is an int and the return is a real. I'd prefer it give me an error outright, rather than assume I meant to do sqrt(2.0), even though I most likely did.

sqrt(2)'s not an error, it should just return 1 :colbert:

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

Boris Galerkin posted:

Ok now I'm being serious because I don't understand/know: what would you have it rather do? If the node index was out of range then it should attempt to read memory it can't read and crash, that's how I see it. The other options would be "assume user meant to access last index" or "ignore this error and keep going." Both seem like terrible options to me.

The option you actually have in C is "attempt to read some random memory, which might crash or might just silently give you garbage data depending on how lucky you get". So an error handling strategy of "definitively exit" seems (at least to me) like a clear upgrade.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
[quote="“quiggy”" post="“476650399”"]
sqrt(2)’s not an error, it should just return 1 :colbert:
[/quote]

I mean how do you take the square root of an integer?

Seriously though, if i allocate memory for an array of 1000 ints and then end up with an array of 1000 real then something is wrong.

Adbot
ADBOT LOVES YOU

fritz
Jul 26, 2003

Boris Galerkin posted:

Ok now I'm being serious because I don't understand/know: what would you have it rather do? If the node index was out of range then it should attempt to read memory it can't read and crash, that's how I see it. The other options would be "assume user meant to access last index" or "ignore this error and keep going." Both seem like terrible options to me.

e: If you say "it should just note the error and exit it gracefully" then well I just don't see the point/how that's different than just segfaulting. It's not like I can do anything about it other than fixing the problem and recompiling/rerunning.

One reason to do error handling in scientific codes so that you can know where the error is instead.
For example, LAPACK logs a message and halts:

quote:

http://www.netlib.org/lapack/lug/node119.html
All documented routines have a diagnostic argument INFO that indicates the success or failure of the computation, as follows:

INFO = 0: successful termination
INFO < 0: illegal value of one or more arguments -- no computation performed
INFO > 0: failure in the course of computation
All driver and auxiliary routines check that input arguments such as N or LDA or option arguments of type character have permitted values. If an illegal value of the ith argument is detected, the routine sets INFO = -i, and then calls an error-handling routine XERBLA.

The standard version of XERBLA issues an error message and halts execution, so that no LAPACK routine would ever return to the calling program with INFO < 0. However, this might occur if a non-standard version of XERBLA is used.

Granted, it doesn't have a lot of info:
code:

    printf("** On entry to %6s, parameter number %2i had an illegal value\n",
		srname, *info);

(http://www.netlib.org/clapack/cblas/xerbla.c)
but it's better than some (like, for example, opencv).

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply