I am also not a fan of hyperliteral case-by-case error checking (it reminds me of my early C code), but it's A Style, one that the Golang team enthusiastically adopts, and it's unlikely to go anywhere.
I am also not a fan of hyperliteral case-by-case error checking (it reminds me of my early C code), but it's A Style, one that the Golang team enthusiastically adopts, and it's unlikely to go anywhere.
More than just the normally used: if err := foo(); err != nil; { return err }
Large code Go code basis are so much better (more maintainable) for "hyperliteral case-by-case error checking". My impression that only people who havn't written large Go projects have this complaint.
Also, there is no reason you could not define a function like:
func foobar() (fatalErr, err error)What I was showing was that:
try {
foo()
} catch (ex1 ExType1) {
...
} catch (ex2 ExType2) {
...
} catch (ex ExBaseType) {
...
}
Type logic is completely possible, which while apparent to you is lost on some people who haven't work with non exception langs before.Underpowered error handling (and I'm not advocating for exceptions, persay), lack of generics (and the resultant copypasta party and interface{} runtime casting [aka, the return of void *]) are real warts in an otherwise fine language.
And I'm not just theorizing: I spend my days writing a large, nontrivial system in go.
I've used Haskell a lot before, and I'm not asking for no nulls or real ADTs (though I wouldn't complain), but generics + better typed errors would really help clean things up.
Meanwhile, a lot of us are just waiting for rust...
It just depends on what you're trying to accomplish, but most use-cases can be accomplished without exceptions. The other use-cases often indicate bad design.
As for generics, you can get 90% of the way there with interfaces. The use of `interface{}`--while sometimes necessary--is often an indicator of bad design.
In large code bases, you often don't need (or care) to know what underlying type something is. For example, you shouldn't care whether an `io.Reader` is a TCP socket, file or completely in-memory ala `io.Pipe()`.
There are times when type assertions are the best/only way to get something done, and that's why they're there, but those cases should be relatively infrequent.
Generics would make some things easier (Rust's implementation is quite nice), but it's not significantly impacting my productivity, and I certainly wouldn't consider switching languages just because Go lacks them.
EDIT: Added info about generics
type URLError {
url String
}
func (e URLError) Error() String {
return fmt.Sprintf("Invalid URL: %s", e.url)
}
At which point a caller that wants to handle this error can either extract the URL to do fun things with it or just dump the Error() string.This is a widely used idiom in Go.
object NonFatalError {
def unapply(err: Throwable) = err match {
case _: TimeoutException => Some(err)
case _: IOException => Some(err)
case _ => None
}
}
def executeWithRetries[T](maxTries: Int)(callback: => T): T =
try {
callback
}
catch {
case NonFatalError(error) if maxTries > 0 =>
logger.warn(error)
executeWithRetries(maxTries - 1)(callback)
case other: Throwable =>
throw other
}
And usage: def funcMayErr(): Int = throw new TimeoutException
val value = executeWithRetries(5) {
funcMayErr()
}
But there's more. Because with generics and exceptions you can actually wrap the whole result in an algebraic data-type that also has handy methods for dealing with failure (e.g. a monad), as in: Try(executeWithRetries(5)(funcMayErr)).map(x => x + 1).getOrElse(default)
Cheers,That it can abstract some other "common patterns" doesn't solve this.
can easily be implemented in Go: http://play.golang.org/p/kMNqfY7LYX
* those casually glancing over to figure what the code does * those actually trying to figure out what a given expression does, either because they are reviewing or debugging * compilers are people too
Conciseness is often overvalued and pursued to the extreme where effort is made first by the author to seek for the perfect oneliner, and then for the reader to actually check that this code is doing what expected.
Composition is important, but I don't think I found great real world examples of composition which wasn't either working only because of a tightly controlled code base or because it was just an example to prove a point.
Don't get me wrong, I love scala/haskell, I find playing with those constructs interesting and beautiful.
It's just that Go is a different thing, is a modern approach of getting back to basics, a minimal toolset for do just programming, more or less translation of thought into instructions.
And it's works very well; it's very easy to get things done quickly and the produced code tends to be easily maintainable. It's easy to have control over the memory footprint. The tooling is very mature (http://blog.golang.org/race-detector, gofmt formatting+refactoring)
But correct code must assume that any function you call might throw; which is why you should use defer blocks e.g. to release resources, instead of C-style "cleanup at the bottom of the function". (Defer is also prettier IMHO)
The execution is suspended, the stack unwound until the first handler, the handler has access to a value that is "thrown". Stack information is preserved in order to print meaningful stack traces.
C++/java/python have syntax sugar that performs a pattern match on the thrown object to decide whether to handle it or bubble it up, while in Go you do it manually, but other that that I don't see much of a difference in the mechanics of them to justify being so pedantic about the naming of the action.
They are used for things like out of bounds indexes, where in C it would simply be a segfault. A panic is a way of gracefully exiting a program that would have segfaulted otherwise. Correct code should check for out of bound indexes either way.
Though you raise an interesting point: Should you check array indexes if the runtime is also checking it for you?
In Java, the runtime is guaranteed to throw an exception and it is relatively rare that you would pre-check the array indexes (you might use assertions in debug code).
Incidentally, array bounds checking is actually relatively expensive, to the point where most JVMs (which use signed indexes) use an unsigned comparison trick to make it one comparison instead of two. So it does matter...
Except you don't. I haven't used recover in any of my code for a long time (more than a year). Most of the time you don't need to worry about handling panics, but you can if you really need to.
> Should you check array indexes if the runtime is also checking it for you?
In Go the generated code does it. You shouldn't do it yourself.
We're seriously drifting off track here, but if we should rely on Go to check array indexes, that seems like you _would_ want a recover block, so that we can map it to a Go-preferred error code?
There's also no such thing as a "recover block" (you're thinking of a "finally block" or "catch block", neither of which exist in Go).
If we thought you should use recover any time there might be an array out of bounds panic, we'd have designed the whole language differently. Panics should happen when things go badly wrong, and most of the time that means your program should crash.
You should use recover only in two rare cases: 1. where you're specifically using panic/recover as a kind of setjmp/longjmp (as it is used within encoding/json, for example), and 2. where you don't want a programming error to bring down your entire program, such as in the base net/http handler (although I think it's debatable whether we should have done it there; but it's done now and we can't change it).
It amazes me that there has been so much discussion over this incredibly minor and seldom-used feature. Just return and check errors (and just panic when things go really wrong) and get on with your life.
Of course this "pattern" should only be used when you're sure that that one failing goroutine won't have a cascading impact on other goroutines that are still running.
As opposed to an error, which can and will happen.