static void scanlevel(int num, FILE *fp)
{
filemap_t filemap;
int i;
if (fseek(fp, mapptrs[num-1], SEEK_SET))
error("Can't load level %d from blockman.lvl", num);
if (fread(&filemap, sizeof (filemap_t), 1, fp) < 1)
error("Can't load level %d from blockman.lvl", num);
if (filemap.startx < 0 || filemap.startx >= LEVWIDTH
|| filemap.starty < 0 || filemap.starty >= LEVHEIGHT)
{
error("Level %d is corrupt", num);
}
map.startx = filemap.startx;
map.starty = filemap.starty;
for (i = 0; i < LEVWIDTH*LEVHEIGHT; i++)
{
map.tiles[i] = (tiletype_t)filemap.tiles[i];
if (map.tiles[i] < 0 || map.tiles[i] >= NUMTILES)
error("Level %d is corrupt", num);
}
}
This shows another benefit of exceptions, which is that if uncaught, they stop the program with a traceback of the exact point they occurred. So it's not even necessary to write most of the checks above. `error` here is a routine that aborts the program; you get that behavior by default with exceptions.Whereas in C/Go if I forgot one of those error checks, the error would occur silently, leaving the program in some weird inconsistent state that I never planned for. It would just do something stupid and maybe crash or panic later on, far from the place where the initial error occurred.
I guess I'm just arguing for exceptions, which is old news as languages that have them have been around for quite a while. But Go doesn't offer much of a substitute of which I'm aware. The explanation of how it solves these problems has not been forthcoming.
When you're writing reusable library code (and when you think about the scale of Google's codebase, they must have an insane amount of these libraries), it's important to make this distinction. There are some error conditions where you really just want to say "if this ever happens, just die, because there's nothing sane to be done", and Go provides panic() for these situations, similar to the error() function in your code above.
For situations where you do want to return a meaningful error to the client, I think Go's multiple return values provide a very good way to do it, far better than the overloading of NULL or -1 that you find in C and C++.
Does panic provide the stack-trace?
If the panic isn't recovered it will print a stack trace, if it is recovered you can get a stack trace with runtime.Caller().
Go's panics are exceptions.
Better than C? Sure. But not better than languages that provide sum types; some of which have been around since the 70s.
There are a lot of great languages that end up mostly academic because they lack whatever the magical balance of features, simplicity and usefulness it takes for a language get mind share.
I suspect Go might have hit the magical balance with channels, strong types, great build system, simple minimal syntax and language keywords, fairly opinionated best practices (and formatting) and static single file deploys.
Sum types can handle multiple return values seamlessly in a typesafe way as a special case, but are not limited to that because they may have different data shapes other than simple products, and callers can be checked to deal with each possible shape at each call site by the compiler.
On a side note, the same people who claim that sum types are "better" are never able to come up with a constructive proposal how sum types could be integrated into Go in an elegant way.
They are "better" in that they are, in fact, more constrained; only when the error case arises will there be any accessible error value; otherwise, the actual expected value will be found. Since go uses an ad hoc product type, you always get an error value and the return value, even if they are mutually exclusive most of the time.
Also, they are both ways to build larger types from smaller ones, and the way they go about doing it is rather obvious from their names, and thus the contrast.
> On a side note, the same people who claim that sum types are "better" are never able to come up with a constructive proposal how sum types could be integrated into Go in an elegant way.
Forgo the cutesy anonymous members for the massive benefits of sum types? For a team which prides itself for its ability to perform trade-offs, they sure were rigid in this stance.
But multiple return values are there for much more than returned result and error. You conflate that with the specific use of returning result and error, and based on that, you claim that sum types are better. That's a straw man par excellence.
I am not a language designer, but I have become interested in languages in the past couple of years. Sum types require some sort of generics implementation, which Go does not have. I think the design choices the authors made regarding the language have made adding generics that much harder, that now they are struggling to find the "Go way" of fitting them into the language.
You can't actually write code like that in Go, by the way. It's not going to let you read data directly into a struct like that, nor should you really be doing so in the first place.
I'd be happy to provide a more detailed analysis, and possibly even Go-equivalent code, but without further context (at least the definitions for `filemap_t` and whatever struct type `map` is, if not a full description of the file format and its meaning), it's impractical.
Sure - and the possibility of exceeding the bounds of an array would be a clue that you need to bounds check, but there's still a hell of a lot of C code out there with array overflow errors. You can argue that people who make these errors are bad programmers, but that's fairly irrelevant - most programmers of any level will end up working with code with errors in it at some point. Exception stack traces are an extremely useful way to find out where something went wrong when someone failed to do some necessary error checking.
I have no experience with Go, so I'm not saying what it does is wrong - I'm just curious. Say a customer experiences a failure with your software caused by some missing/incorrect error handling, what do you do to work out what happened?
Not possible in Go, the runtime will panic.
> there's still a hell of a lot of C code out there with array overflow errors
An extremely easy error to make in many cases, which is why modern programming languages bounds-check.
> Exception stack traces
Go will give you a very nice stacktrace should it ever panic.
> I have no experience with Go
Which is really the problem. People keep arguing about Go's merits based on no substantive understanding.
It's very obvious which operations can fail without a panic in Go, because functions explicitly return error objects -- actual error objects, not magic numbers. The return signature for the Go equivalent of fread is (int, error), not (int).
Is -B for disabling bounds checking no longer supported?
Looking at the code it seems it has been removed.
> ... I would certainly never enable such a thing in real code.
So I assume you don't do C or C++. :)
While I agree with you, there are certain cases where it might help. That is why most strong typed languages with native compilers allow to selectively disable bounds checking, since the Pascal/Modula-2 days.
I only support doing this if profiling proves it is really worth it, give the security issues.
If C had a simple universal switch for bounds checking, I'd turn it on everywhere and immediately revoke commit privileges for anyone on my team who turned it back off. But it doesn't, and necessarily can't, making your statement nothing more than an annoying exercise in wrongful pedantry. It is contextually obvious I was talking about Go code and/or languages/compilers with such a switch.
package main
func main() {
slice := []string{"first", "second", "third"}
println(slice[1])
slice = slice[0:2]
println(slice[2])
}
Compile with go build -gcflags -B wat.go
./wat
Output second
third
Without the `-B` you'll get a runtime panic. I'm on Go 1.1. (Maybe it's removed in tip?)Next time I better checkout and do a proper grep.
go tool 6g -help
The `-B` switch will disable bounds checks. You can use it like this: go build -gcflags '-B'They are not arguing, they are asking questions to improve their understanding.
The context of the thread, however, very much is people arguing. See for example graue's post a few levels up.
It'd be interesting to see what you'd do with the code above in C++, and where you'd put the error handling code for diverse errors that might occur reading this particular file.
The Go approach is to handle errors locally, often in the calling method, which makes it clear where they are handled and what the outcome is, and easier to recover gracefully, without unexpected exceptions from code in libraries or other code in the program. Some large users of C++ (like Google) refuse to use C++ exceptions in their own code - so they are not entirely without controversy.
In the code above, if you used exceptions, and relied on the libraries to throw exceptions for errors, you'd have to throw your own exception at:
error("Level %d is corrupt", num);
So you'd have a mix places where exceptions were generated (in unknown lib code, in your code) and an unknown (for the reader) mix of places where they are handled. I'm sure this could be done gracefully, but it does mean errors missed might be handled at a much higher level in the code, far away from where they were generated, which can lead to errors being missed until it is too late to do anything but output a stack trace and exit, which to the user seems equally stupid as crashing or panicking at some later point.
If you exit the program on simple errors like being unable to load a single game file, it's not very pleasant for the user - I'd expect it instead to recover gracefully and show the user an error before continuing, which is easy enough when using Go's pattern of error returns, and harder with exceptions where you have unrolled the stack possibly past the loading code, unless you start handling exceptions in calling code one level up, which looks very much like the error handling of Go. So there are trade-offs to using either method aren't there?
They even acknowledge in their C++ style guide[1] that: " Things would probably be different if we had to do it all over again from scratch."
[1] http://google-styleguide.googlecode.com/svn/trunk/cppguide.x...