14 ms·
Errors are values
- rtpg 12y ago>We recently scanned all the open source projects we could find and discovered that this snippet occurs only once per page or two, less often than some would have you believe. once per page? that's once per 30 or so lines. 2 or 3 percent of the code is this same repetitive structure? Also, only takes one time to forget this for everything to fall apart. We've spent so much time trying to deal with uninit'd pointers and things like that on the language level, and here we have something that's much simpler, yet super repetitive and should be easy to catch on compile time. Maybe I'm just a bad programmer, but I really prefer the Java "force you to catch exception" model much more to this. >It's worth stressing that whatever the design, it's critical that the program check the errors however they are exposed. If it's critical to check the errors, I think that it's worth writing language rules in a way to force people to write a "has error " code path (think pattern matching). I get go is trying to be as small of a core as possible, but I think there's a lot of value in extra compiler checks (with no runtime cost!) to catch this sort of thing.
- twotwotwo 12y ago> I really prefer the Java "force you to catch exception" model much more to this. https://github.com/kisielk/errcheck/ https://github.com/kisielk/errcheck/ will tell you if you silently discard the `err` value (though you can still explicitly assign it to _ to ignore). Seems worth using for a lot of projects.
- burke 12y agoWe use errcheck for all the Go code we write at Shopify. In many cases, we compile the project using a script that fails if errcheck isn't clean, effectively turning into a "you are forced to catch the exception" sort of idea. It's nice because it's still easy to ignore the err in certain circumstances (e.g. the occasional `_ = f.Close()`), but there's always a visual marker to remind you.
- jroesch 12y agoThe above comments are just pointing out that the compiler should do this for you. It is great that there are third party tools for this, but a simple lint like this should come for free, especially when this is the endorsed error handling policy.
- enneff 12y agoNot everything belongs in the compiler. Go's "vet" tool might be the place for something like this. But, honestly, I have been writing Go code for 5 years and I can count the number of times that "didn't check the error" was the cause of a bug on one hand.
- twotwotwo 12y agoAgree on not in the compiler. A ton of folks seem to dig errcheck; having that sort of check in vet or in the Go distro somehow seems worthwhile. (Of course, if I feel that way, maybe I should put some effort where my mouth is and try and start on a patch. :)
- Intermernet 12y agoI'm actually quite surprised this functionality hasn't made it into "go vet" yet. It would be useful to be able to do this without a 3rd party utility (although all respect to kisielk for producing such a useful tool).
- rtpg 12y agopleasantly surprised that you could write go tooling to check this, I hope the team is willing to pull things like this in.
- deleted 12y ago[deleted]
- bsdetector 12y ago> that's once per 30 or so lines. 2 or 3 percent of the code ... I really prefer the Java "force you to catch exception" model much more to this. And that 2%-3% doesn't include where errors should have been checked but were not. For instance run programs without stdout ("./program 1>&-") and most Go console programs will just happily write to nowhere whereas almost all Java ones error out immediately. Closing stdout doesn't often matter, but sometimes things like this are important. I don't understand how in the 2010s it's okay to make a language with worse reliability. And worse "JavaDoc" equivalent. Go should be better than C and Java in every category and it's just not better in way too many.
- sangnoir 12y agoLanguage design is about trade-offs. Broadly speaking: being good in one category means being bad in another.
- TheDong 12y agoWhile your comment is sort of true, it's not applicable here really. We know how to handle errors better than this. Go's error handling is C style. We've since found exceptions, Maybes, and friends which are very powerful. What you said implies that when Rust chooses to use Maybes they're somehow being bad at something. That's simply false; computer science as a field has advanced and one of these advancements (not even that recent of one) is improved understandings of how strong type systems can be used to make code, including errors, safer. Go completely ignores this of course and they're not doing it for a tradeoff that I can see. They gain nothing in terms of performance for not having this type (everything is already a reflect/Value anyways, so much for not having the baggage that you'd need for generics) and everything in terms of having a worse language. Being good at something doesn't instantly mean you're bad at something else. In fact, I wouldn't even claim that's the general case. If you disagree, you're welcome to point out the tradeoffs at work here. I think your comment is just a vaguery that is completely untrue in this instance.
- enneff 12y agoThe corresponding loss to Rust's gain is language complexity.
- cmelbye 12y ago> here we have something that's much simpler, yet super repetitive and should be easy to catch on compile time. It is easy to catch on compile time. https://github.com/kisielk/errcheck https://github.com/kisielk/errcheck
- curun1r 12y agoGo's error handling is only a problem because of other missing features...algebraic types and/or generics. With either of those features, you could implement two-track, functional composition with a single bind method. That would go a long way towards only having to write your "if err != nil" once rather than once per function invocation. Alas, the lack of those other features leads to articles like this one that offer unnatural ways of avoiding something that is pretty much idiomatic in the language.
- RandomCode 12y agoErrors are exceptions at my command-line.
- modernserf 12y agoThis looks suspiciously like implementing Maybe every time you need to deal with a series of errors.
- tel 12y agoIt's exactly that (well, EitherT Error IO () perhaps) and the pattern would be far easier to replicate if Go had generics. So we're back here. Edit: I wrote an elaboration, https://news.ycombinator.com/item?id=8877732 https://news.ycombinator.com/item?id=8877732
- thomasahle 12y agoYep, I also think that's a good way to think about it. But I don't think implementing that one or two times for an entire program is too bad if it forces you to think error handling into your design.
- inglor 12y agoMaybe and Either constantly show up when people discuss patterns to make error handling less clumsy in languages without exceptions baked in (and in asynchronous contexts in some languages with exceptions) - I just wish posts like this named what they're talking about better - Rob Pike is undoubtedly aware of the names.
- deleted 12y ago[deleted]
- theseoafs 12y agoI would not be so sure Rob Pike knows about Maybe and Either. How would he?
- deleted 12y ago[deleted]
- TheDong 12y agoOh, I dunno, maybe the fact that he designed a programming language which practically proves he must have interest in programming languages. Combine that with the fact that Maybe/Options are neither new nor unusual in language design and fairly heavily discussed and I think it would be practically impossible for him to not have known about them a decade ago. Even failing at that, he would doubtlessly have heard of them since because people discuss them in relation to Go so often. So yeah, that's how he would know about them. They're extremely common in two areas of his interest (Go discussion/criticisms, language design).
- latch 12y agoI'm a Go developer, but I don't buy this. At best it's showing how, with a bit of work (often times by the developer), errors aren't that bad. But I don't see how it's showing that it's better? If you log the error when checking if ew.err != nil, which might be many lines or even a different function, you'll have a hard time getting a meaningful context (which is hard to do in Go in the best case), like a stack.
- axman6 12y agoOne way I could see to make this more generic is to provide the errWriter strict with a function to call on the error if it's not nil, but i'm not familiar enough with Go to know how we'll thug might work.
- enneff 12y agoThe point is not to use this exact pattern, but to write code to handle errors in a way that works in your context. If the error check is many lines away or in a different function, you should use a different approach.
- twotwotwo 12y agoThe stdlib's compress/bzip2 internally does the same sort of thing where you do a bunch of operations and then check for errors: http://golang.org/src/compress/bzip2/bit_reader.go#L12 http://golang.org/src/compress/bzip2/bit_reader.go#L12 And regexp uses panics to propagate parse errors up the stack internally, and that's even described in Effective Go: http://golang.org/doc/effective_go.html#recover http://golang.org/doc/effective_go.html#recover In both cases an error value is returned to the package user. In general, I think you should feel freer do abnormal things that suit your situation within a package as opposed to across API boundaries. The standard ways of doing things should still be preferred when it's not too costly: the standards are what they are for a reason (even if the tradeoffs they represent are more appealing in some situations than others), it's easier to read code that's written in the way everyone's used to, and standards help keep you from spending time bikeshedding unimportant details. But the surface area of your package is the first thing users will have to learn, and what you have to keep in your head whenever maintaining code that uses your package--that's what it's most important to make clean and normal-looking and consistent. I know the post talks about using an unusual error-handling pattern in an API, and you can do that sometimes, too. Just trying to make a separate point--when you're thinking about doing unusual stuff, keep in mind there's a big difference between an unusual implementation and an unusual API.
- Rapzid 12y agoPanics can be incredibly useful. I use them in HTTP services to simplify returning error JSON objects to the consumer. In general I'm a bit surprised about all the hate(I say hate because of how people react to it and react to suggestions for how to better work with it) the error system gets. I find that creative use of error/panic can usually scratch my "this is too much error handling code" itch when it flares up. As long as it's documented in the API I agree that unique public error handling strategies are not all bad. Interestingly I write a lot of C#, which of course has try/catch/finally, and I find that if you are doing it properly you have many levels of handling and a strategy involved at the different levels for what should done when. I find that many people write TERRIBLE error handling. There may be try/catch sprinkled around but there will be no strategy to it with edge-cases and improper handling abound(masked exceptions, finaly's throwing new, unrelated exceptions, etc). I personally find the amount of effort required into developing and executing a robust error handling strategy to be about the same between C# and Go. That's not to say that I don't sometimes wish Go had C#'s error handling( ;) ), but I realize they are different beasts with different masters.
- zzzcpan 12y agoHow about just using beloved tables instead? Maybe even with function literal to prepare some stuff, if you need to. for _, s := []string { p0[a:b], p0[c:d], // ... } { _, err = fd.Write(s) if err != nil { return err } } As others already implied it could have been cleaner with exceptions, but I don't think this is that one case where exceptions are particularly important. There is no recursion yet or anything like that.
- chrisfarms 12y agoOne thing I love about the repetitive err pattern is that I find my self wrapping/appending errors with useful context more frequently (rather than say catching an exception five frames further down and just appending "mylib: oops"). I realise the later was just laziness on my part, but the constant err checking really does force good habits on me, which of course yields higher quality code that's easier to reason about when something goes wrong.
- rdtsc 12y ago> (rather than say catching an exception five frames further down and just appending "mylib: oops"). That is why I like exceptions, so I don't have to explain possible errors like this if I don't have to, but can get a top level exception and see a stacktrace with it. Then choose to add aditional info. I really like that pattern and use it quite a bit. For example, it is easy to turn code that connect into a piece of code that re-connects with a back-off without having to modify lower layers of the connect call itself (say because it is already in a library).
- tel 12y agoErrors are absolutely values and that's a great perspective for any language to take. Essentially, you can see an errorful computation as "returning" either a valid return value as expected or an error. In Go the quotes are eliminated: that is exactly what happens ret, err = fd.Write(...) Above we have two (initial) possibilities 1. The return was error-free, err = nil, and ret is an interesting return value. 2. The return was errorful, err != nil, err instead details the reason for error as an integer, and ret is nonsense. This is a little challenging because we must be sure to check err in the second case since otherwise ret is silently meaningless. This is a source of bugs called "Boolean Blindness". In a language with Sum types we'd represent this by saying that errorful computations adjoin an error type to the return type. Int ----> Either Error Int What's nice is that this automatically ensures the above invariant of err/ret is held: a value of type Either Error Int is either "Right i" for some valid Int i or "Left e" for some error value e. The first thing we check is the handedness of the result and thus know the result of the computation. Sum types like Either solve Boolean Blindness because we literally cannot get access to our return type Int unless it is valid: if we find that the result is "Left e" then all we have is an Error value. (Ultimately what this boils down to is that product types like (ret, err) behave very differently from sum types and cannot replace them.) If Go had generics we would also notice that the error-passing behavior of Either is naturally ignorant of the particular choice of types Error and ain't. We could manipulate it meaningfully (and indeed write the functionality used in this article) for any and all values e and r as the type Either e r. Then upon use Either e r would specialize to Either Error Int. It's easy to argue that this is silly: we only need one error type and it is not difficult to replicate this code. I'll argue for now that only having one error type is at best confusing and at worst a lie. Different processes and functions have different exceptional states. Coercing all of this variation in error meaning into the same error type ensures that misunderstandings and accidental unifications will occur. Honestly, I really love Go for making sure that errors were values: it is an incredible step forward for making exceptional cases more reasonable. Unfortunately, Go does not seem expressive enough to build the machinery which makes this style of error handling all of reasonable, non-repetitive, and non-confusing.
- Arnavion 12y agoThat second-last example with errWriter destroys safety AFAICT, since it requires the user to remember to check for ew.err to see if the whole sequence of operations actually succeeded or not. Atleast the last example with flush returning the error made sense, since that's an explicit "commit" operation that the user must call for correctness. However not every series of operations has a final commit operation, so this technique is unsafe in general. (In fact even flush might be optional? I didn't check the docs.) Edit: The docs for flush say nothing, but the docs for the Writer interface itself ( http://golang.org/pkg/bufio/#Writer http://golang.org/pkg/bufio/#Writer ) do: > After all data has been written, the client should call the Flush method to guarantee all data has been forwarded to the underlying io.Writer.
- enneff 12y ago> That second-last example with errWriter destroys safety AFAICT It is possible to make mistakes. Go isn't the kind of language that forces you to be safe and do the right thing all the time. Like all languages, it makes tradeoffs.
- overgard 12y agoI get being against exceptions in a language like C++ where it can leave your program in a really weird state and have some ugly performance implications, but I don't get the argument in languages where it can work just fine. The argument seems to be "you should be checking for errors anyway", but I feel like there are basically two types of errors: the obvious kind that you're going to check for anyway (or get bug reports), like network disconnects and bad files, and the "how the hell did that even happen" exception. For the obvious exceptions it seems like a wash, other than the try catch block being a bit cleaner, but for the "hell the hell did that happen" error exceptions seem way more useful. I'd much rather my program explode immediately than risk someone letting the error passing silently because people didn't check err because like "that will never happen". I know, functions can call panic and all that, but it just seems clumsy for no net gain. I do think there's one type of error checking that doesn't get enough love (I don't know if Go has this, but I'm guessing probably?). "assert" that gets removed in release builds. It's kind of silly when you're working alone, but in a team of programmers or for libraries it's awesome, because it's basically like runtime documentation that gets compiled out. So you can warn your fellow devs about things without it bloating your program. The newer C++ has a really great feature called "static_assert", which is basically a compile time assertion, which can be handy for things like checking in various places that maybe if the count in enum changed, that you accounted for it in another spot of the code, or that certain aspects of a new platform sit right, or things like that.
- deathanatos 12y ago> I get being against exceptions in a language like C++ where it can leave your program in a really weird state If you're programming in modern C++, then exceptions allow you cleanly (as in, clean code) leave your program in a very well defined state. You have to adhere to RAII, and there's a lot of C++ shops out there that don't allow RAII (and are wary of exceptions, because they ignore RAII). I've routinely used exceptions in C++ as a both efficient and clean way to propagate errors out to a stack frame that can handle them, fully confident that RAII will clean up as I go. > and have some ugly performance implications More than having an error object that the calling frame needs to check? If I understand Go (and, since it's mentioned here and I think is relevant, Rust's) setup, each stack frame between the frame that can handle the error and the one where the error occurred must check that an error did indeed occur, and if so return it. Rust makes this pretty through macros, and the article has some good thoughts on it for Go. Nonetheless, can the compiler compile that to better than an `if error { return error }` type check? My understand of C++ exceptions is that this was basically how things _were_ done (but no longer). Previously, exceptions were managed by "sjlj" (short for setjmp, longjmp, the calls used to manage exceptions); any frame that _could_ have an exception would need to call setjmp on entry, and this was a bit hefty. However, my understanding is that, at least for g++ on Linux, C++ has abandoned this in favor of a newer scheme called "dwarf2"[1]; this scheme is such that entering a "try" (or a stack frame where RAII would need to do actual cleanup) is free (rather, it is covered as part of the normal stack setup, really), leaving it gracefully is also free; only during actual stack unwinding do exceptions add code/performance penalties. I think there is an unfortunate amount of information that C++ exceptions are inefficient left over from the sjlj days. [1] http://www.x86-64.org/documentation/abi.pdf http://www.x86-64.org/documentation/abi.pdf
- inconshreveable 12y agoI disagree. Of the various approaches I've seen to cope with this problem, Rust's try! macro is the most elegant. Given the practical minded nature of Go and the core team's willingness to add in special cases to the language where appropriate, I think a Go version of try!() would be welcome and fit with the ethos of Go. It solves the 80% case and it doesn't mean the creation of a type-safe macro system. The thesis of the blog post is that errors are values and thus you can program around the repetitive handling of them. But the proposed solution of writing a one-off implementation of Maybe like `errWriter` isn't an answer because: 1) Most importantly, when you need to call 3 functions which return differently typed values and can fail, there's nothing in the language to help you. This is the common case. 2) The implementation is now less obvious. To understand it, I need to read the `errWriter` helper code. 3) The code space you'll save by writing a new Maybe type is negligible.
- falcolas 12y agotry! isn't too hard to emulate in Go, simply write a wrapper that panic()'s on and error and passes the first value through. The rust Result idiom is easy to implement as well, and is used frequently in the database.sql package to handle nulls. In this way, rust and Go are not that different. The biggest difference is the lack of a generic Result, which means you have a NullString struct with a Valid and String method.
- nostrademons 12y ago> try! isn't too hard to emulate in Go, simply write a wrapper that panic()'s on and error and passes the first value through. Several such wrappers are present in the Go standard library, eg. template.Must: http://golang.org/pkg/text/template/#Must http://golang.org/pkg/text/template/#Must The thing is, without generics, you need to write a new wrapper for each type you're dealing. Add to that that this really isn't idiomatic Go style (code that panics all the time is frowned upon), and most people don't bother. This is a pretty good illustration of both the point in the article and why some folks don't like Go. It's programming with error values; you can decide exactly what you want to do with the error, including panicking when you encounter one, and use the language's own abstraction mechanisms to write that code. OTOH, the reason why we have exceptions in most mainstream languages is because many, many C programmers wrote the same error-handling code over and over again, and eventually someone decided that it ought to be a language feature. The point of a programming language is to abstract out common patterns that you'd otherwise have to write repetitively.
- chrismorgan 12y agoWhat in Go is a return type of (Anything, error) with typically exactly one of the two being nil is in Rust the Result type, `enum Result<T, E> { Ok(T), Err(E) }`, something which is explicitly exclusively-either an OK T or an error E. The equivalent of `x, err = expr; if err != nil { return err }` is very common in Rust; it would be written `x = match expr { Ok(x) => x, Err(e) => return Err(e) }`. It’s such a common pattern that a macro named try! was placed in the standard library for this incantation. Therefore you can write `x = try!(expr);` (or if you’re going to ignore the Ok variant, just `try!(expr)`). Rust also warns you if you do nothing with a Result object; you can ignore the warning or suppress it, but it all makes it very much harder to ignore such error conditions. Go is one step up from C, where integral error-indicating return codes are the norm and are typically ignored, but I feel it’s still a fair way off the confidence that Rust gives me that I haven’t accidentally forgotten to deal with an error case. If you want people to get the message “Whatever you do, always check your errors!”, it helps if you design the language to make it hard for the user to forget. (You people that know Rust, I’m ignoring the `FromError` part for simplicity. That’s only in libstd’s try!, anyway; libcore’s try! doesn’t have it.)
- tel 12y ago(And with HKTs you can abstract this whole blog post into a fully generic one-liner applicable to basically any notion of error! I can't wait til Rust picks those up.)
- tpaksoy 12y agoDoes Rust's (future) HKT implementation have an RFC or a PR available?
- steveklabnik 12y agoIt does not. It's entirely speculation at this point, though many, many of us want to have it. Everything has been so focused on getting 1.0 out the door, and HKT should be backwards compatible. Special pains have been taken to consider HKT when designing APIs.
- 12y ago
- jimjimjim 12y ago"I was wrong about Gopher's Law. The probability of mentioning generics in a #golang discussion starts at 1 and raises with time." - Dave Cheney. Article posted 1 hour ago. Ctrl-F Generics = 3 matches
- ekimekim 12y agoTrue, but I fail to see why it's relevant. Every mention of generic here (at time of reading) is along the lines of "this pattern could be generalised if we had generics", which IMO is a fair point to make when discussing ways to make a repetitive design pattern less painful.
- artursapek 12y agoI don't know if I'm just ignorant, but I like that Go forces me to use real estate for dealing with errors. It's the first language I've used where I give as much attention and design to the "sad paths" as I do to the "happy path". And I'm very comfortable navigating through text files and folding blocks of code, so it doesn't bother me if handling an error costs me a few lines. Macros like try! might take up less space, but is that really a bottleneck for your work?
- eropple 12y agoPersonally? Line noise sucks. Constant error checking is vertical line noise, which is even worse; it distracts me from important things like "is my business logic correct" with "did I type that particular error check correctly?". For me, that really sucks. (I get other people like it. That's great. For me, it's junk.) I generally use Scala and just chain through Try[T] monads and address the sad path right at the bottom, right next to the culmination of the happy path. I always know where it is, I can't miss it, I'm not repeating code all over the place and running the risk of screwing up with part of it...to me it's just a smarter way to handle the failure case.
- jalfresi 12y agoPersonally, I don't believe error checking is line noise; it is part of your business logic. Forcing me to acknowledge and deal with error cases requires that I write more robust code, that I acknowledge the failures and errors that can occur within my logic, as they are part and parcel of that logic. If you see only the success path as the "true code" then you are missing the whole raft of other conditions that can occur; its an idealised idea of what the code is. This is why I like Go. Its the first language I've ever used that forces me to think pessimistically about my own code.
- lmm 12y agoScala makes the distinction clear by having the difference between "=" and "<-" (or "←"); the former for something guaranteed to succeed, the latter for a possible error. This is wonderful; it's obvious enough that you see it (and the compiler catches you if you get it wrong), so when an error happens you can immediately see which functions might be causing it and which ones can't be. But you don't have to write a bunch of identical lines; one or two characters are enough for something this common. And you can reuse the same syntax for other error-like contexts, rather than having syntax for this one piece of functionality that's special-cased in your language and no way to handle similar cases (e.g. audit logging).
- wyager 12y agoRather, (in Go) errors are values that violate type safety and destroy any correctness guarantees the compiler makes. Languages like Rust and Haskell do this right. Both make heavy use of optionals for failable functions, and Haskell's support for HKTs (and therefore Monads) allows awesome stuff like Maybe or Either chaining. Monads allow you to write code that, syntactically, looks like you're not doing any error handling. In reality, the error handling semantics are baked in at the Monad level. This makes sense, because error handling is usually very repetitive (e.g. "Check if the function we just called failed. If it did, return early. Otherwise, keep going." ad infinitum). As a (pseudcoded) example: parseTimeString :: String -> Either Error Time parseTimeString string = parse string $ do hours <- readHours semicolon minutes <- readMinutes semicolon seconds <- readSeconds return (Time hours minutes seconds) If any of those steps fails, the entire computation will stop and the error message will be returned. This is roughly equivalent to checking for "nil" (in Go) after every function call, except you don't actually have to do it. This is defined in the code for the Either monad, which is at https://hackage.haskell.org/package/base-4.7.0.2/docs/src/Data-Either.html#Either https://hackage.haskell.org/package/base-4.7.0.2/docs/src/Da... . As you can see, it's 4 lines of code. (Side note: this is pretty much how you write parsers in Haskell using the Parsec library)
- JadeNB 12y ago> Both make heavy use of optionals for failable functions …. Haskell doesn't do so enough, though (I don't know Rust and so won't comment), so that we still need manually to choose to use the safe library (http://hackage.haskell.org/package/safe http://hackage.haskell.org/package/safe).
- tome 12y agoYes, some unfortunate historical choices there.
- JadeNB 12y ago> Yes, some unfortunate historical choices there. Do you know anything about the motivation of those choices? Surely it won't have escaped anyone's notice, especially not the designers', that this is a wart on the language, and that there is an easy solution; so I can only imagine that the easy solution was rejected for its inelegance—which seems strange, given the devotion to theoretical purity of the rest of the language. (I contrast the situation here to that of the numeric tower, which is clearly flawed but for which, even now, the 'right' approach is not so clear.)
- deleted 12y ago[deleted]
- thegeomaster 12y agoLua uses this to handle errors---I don't find it really tedious. A common pattern is kind of the same---to return either one return value and nil and then an error message or an error code (Lua, like Go, has multiple returns) in case of errors. This results in code like let res, err = some_function() if res == nil then -- you can also check for err ~= nil like in Go -- return from this function with something, err contains more -- information end I think it's very neat---you can also return nil, err from this function if you can't deal with it and some code up the stack should be able to handle it in a meaningful manner. I don't find it particularly tedious; i.e. not less tedious than writing try..catch blocks, and you can clearly see where are the function exit points, whereas with exceptions, any line can theoretically throw an exception. A missed error check can be troublesome, but generally using nil as a value further down in the function will hopefully trigger another error which might be then caught so you can spot the bug. Lisp's continuations are also IMHO a great mechanism---you catch the error and then you can tell the function down the stack what it should do: you can abort, or you can try fixing the error and continuing, or you can try again for some number of times and then report a new error if that fails. It's more flexible than stuff I've heard of before.
- shiro 12y agoJust curious---doesn't that pattern inhibit you to return nil as a meaningful, valid result? Or is nil treated as invalid value in Lua culture?
- kazagistar 12y agoNeither. It just basically never happens that you want both error reporting and a maybe (nil or value) from the same function. Not in list? Nil. Could not parse? Nil. Iteration ended? Nil.
- harryh 12y agoIf a function be advertised to return an error code in the event of difficulties, thou shalt check for that code, yea, even though the checks triple the size of thy code and produce aches in thy typing fingers, for if thou thinkest ``it cannot happen to me'', the gods shall surely punish thee for thy arrogance.
- joshuaellinger 12y agoUpvoted -- you got the tone of go exactly right. We're headed back to C#. Don't like religion in politics or programming languages.
- harryh 12y agoIt's actually a quote from this document which is very very old: http://www.lysator.liu.se/c/ten-commandments.html http://www.lysator.liu.se/c/ten-commandments.html
- Animats 12y agoGo lacks a construct comparable to "with" in Python, or WITH-OPEN-FILE in Lisp, to guarantee that things get closed out on scope exit. Go has "defer", but that's a clunky replacement. You have to explicitly write the "defer", and it's always function-scope, not construct-scope. Most of the problems with exceptions in C++ come from the C++ memory model - allocation and deallocation in exceptions are usually painful, and care must be taken to clean up and unlock things. Go doesn't have that problem - it's garbage collected. Go does have "panic", which is Go's answer to "longjmp". People keep trying to use that as an exception mechanism, which is not a good thing. Python seems to have the best track record with exceptions. Python's usual problem with exceptions comes from library functions which, under some rare circumstance, raise an unexpected exception and take down the whole program. This is mostly a legacy problem due to the poor original design of Python's exception hierarchy. You would like, for example, for everything that can possibly go wrong with an HTTP read or a network operation to be a subclass of EnvironmentError, as caused by an external event or data. That wasn't the case originally. There are still problems in Python 2.7 with getting a ValueError because some low-level data item was bad UTF8 or something like that. The trouble with the "errors are values" concept is that error details tend to get lost as errors propagate upward. This came up yesterday on HN in connection with network errors for GoGo's airborne networking service. I once argued that D's error type should have a "why" pointer slot, so that, when you passed an error upward, you could link the lower-level error to the higher level error. The error message printer would then list out the errors, yielding something like Unable to complete transaction because database update failed because database connection was lost because of network error "Host down".
- spullara 12y agoThat is exactly Java's .getCause() method on wrapped, propagated exceptions.
- mercurial 12y ago> Go lacks a construct comparable to "with" in Python, or WITH-OPEN-FILE in Lisp, to guarantee that things get closed out on scope exit. Go has "defer", but that's a clunky replacement. You have to explicitly write the "defer", and it's always function-scope, not construct-scope. The problem of avoiding resource leaks is somewhat orthogonal to error handling, though. Do note that Python's "with" statement doesn't play well with "yield", which is an important gotcha. But in practice, it's quite convenient.
- callesgg 12y agoThe handling of errors in go is weird if one only returns or breaks on errors. But it works fine if one does something on error, in almost every error check in my go code I have a log.Println with a human readable text like "could not read from the application configuration file." much nicer to get errors like that than a stack dump and a message like could not read from file $filename.
- Terr_ 12y agoIf you want to force the consumer to consider an error case and make some decision about it... Well, I actually like Java's checked exceptions. You've got to emit an error somehow and static checking ensures the consumer won't accidentally ignore the case. (Deliberately, perhaps, but that's a people-problem rather than a technology one.)
- lectrick 12y agoSaying it's so doesn't make it true. You can declare that an error is simply a special value returned and expect people to check for that value all over the place, or you can declare that errors are (literally) "exceptions" and should therefore throw.
- NateDad 12y agoYou can ignore error values returned just like you can ignore the numeric value returned from atoi. That's the point. They're just values. You are the programmer, it's your job to decide what to do with any particular value returned by a function.
- prodigal_erik 12y agoIt's also my job to decide what the square root of "fish" should be. What I do is use a declaration to mechanically verify that cannot ever happen. I never ever want to silently ignore an error, so why shouldn't I be able to declare that as well?
- sepeth 12y agoI think Peter Norvig's quote "Design patterns are bug reports against your programming language" is a perfect fit for this situation.
- Argorak 12y agoI'd really like to see a reputable source for this statement. All I can find are people attributing that quote to Peter Norvig. What I can find, is this: http://c2.com/cgi/wiki?AreDesignPatternsMissingLanguageFeatures http://c2.com/cgi/wiki?AreDesignPatternsMissingLanguageFeatu...
- kyrra 12y agoLooks like it's from a slide deck[0] (maybe a presentation) he did. I know it's not the exact quote, but he may have said it in a talk? (slide 4) "What Are Design Patterns: To avoid limitations of implementation language" [0] http://norvig.com/design-patterns/ http://norvig.com/design-patterns/
- kyrra 12y agoLooks like the site is down now? Archive.org link for it: https://web.archive.org/web/20141224143931/http://norvig.com/design-patterns/ https://web.archive.org/web/20141224143931/http://norvig.com...
- millstone 12y agoI don't get it. How is "errors are values" different from anything? In what language are errors not values?
- NateDad 12y agoIn languages where they are exceptions and are not returned, but thrown and require special syntax to handle.
- millstone 12y agoBut exceptions are values too, i.e. you routinely have an object of type IOException. All of the patterns presented in the article can be done easily with exceptions. I think maybe "errors are values" is a general principle and not meant to be specific to Go.
- alecbenzer 12y agoExceptions are values too but they're more than values. "Errors are values" means "errors are values so just deal with them like you do other values".
- nbevans 12y agoThis article is bizarre. I've read it twice expecting an actual solution to null checking basically everywhere and it doesn't actually present one. I'll stick with discriminated unions in F#, thanks. Errors are values, correct. But it seems hardly any languages actually bother to grok the concept. If your language doesn't provide type safety for ensuring that you capture all resulting conditions then it has failed its job.
- woah 12y agoSomehow the vast majority of code is written in broken languages?
- prodigal_erik 12y agoSounds about right, since the vast majority of code is unreliable crap.
- nbevans 12y agoYes. Along with the vast majority of programmers being absolutely terrible at their job, to such an extent that they can't even choose an appropriate tool (language) to do their trade with. Just look at how OpenSSL is written in C++. And look how that turned out. OpenSSL indeed. Wide-fucking-openSSL would be an even better name. Open to OSS contributions it is not; because the codebase is in such a terrible state. What is the point in OSS if nobody can contribute because it would take a year just to understand the ridiculousness of the codebase, right down to trying to understand why they chose such an inappropriate language for creating a supposedly verifiably secure software system.
- h8liu 12y agoI think explicitly handling error like go is a good thing. Error handling is a tricky thing that needs thought to get right and should be expressed clearly in the code. My only complaint is that `if e != nil { return e }` takes 3 lines of code. Could `gofmt` format that into one line with no line breaking, I am already pretty happy. If they could add a keyword like `ereturn` that only returns when the last parameter (must be error) is not nil, just as an alias, I would be totally satisfied.
- mercurial 12y ago"Error are values", OK, but as a non-Go programmer, how do you handle idiomatically the most common case of wanting to bubble the error up the stack and turn it into something human-readable? Bonus if you do not break encapsulation. Double bonus if you get a stack trace out of it.
- tomblomfield 12y agopanic, recover, runtime.Stack
- al2o3cr 12y ago"There is one significant drawback to this approach, at least for some applications: there is no way to know how much of the processing completed before the error occurred. If that information is important, a more fine-grained approach is necessary." A more fine-grained approach which is conveniently omitted from the article, BECAUSE IT'S SPAMMING `if err != nil` EVERY OTHER STATEMENT...
- NateDad 12y agoThis is required for fine grained control of exceptions too. You have to wrap each statement in a try/catch which is even more verbose than the error check in go. Otherwise you can't programmatically tell which statement failed.
- prodigal_erik 12y agoJava has been recording stack traces for over a decade. This is an argument for building things into the language, because it would have been very difficult to roll our own version of it as a library.
- rvirding 12y agoBeing an erlang programmer I am so used to programming for the correct case and not to check errors but let the process crash when you get an error or, rarely, use a try that I find this style of programming completely wrong. It is what we are trying to avoid. You will always miss cases.
- TheHydroImpulse 12y agoThere are many more areas where you simply cannot abort the process on any error that has been triggered. Erlang, being in the niche of network software, can get away with this because you can have mechanism that monitor the process and it's not the end of the world if one process goes down. Imagine if your Browser crashed every time it hit an error, like a network connection issue, writing to a file, etc...! That just wouldn't be tolerated by consumers of the software.
- fenollp 12y agoEh. The point is to not handle things you don't need to. There is of course a point in handling special cases (like the ones you cited). But compare Erlang's declarative & to-the-point style with Go's `…,err = …; err != nil` and you will quickly realise how much defensive programming sucks to read.
- lazzlazzlazz 12y agoPike's solution does not help very much, and only exposes how little work the Go compiler is actually doing to ensure your code is safe and sensible.