Hello there. I've been reading TDWTF for a while now, but I didn't bother to register on the forums up until now. I'd like to answer to most, if not all, of the issues the OP raised here about the go language. The things I want to point out are factual errors, not opinions or subjective things. I will try to be the least offensive as possible, and I apologize beforehand if I fail at this.
Exceptions aren't thrown in the traditional sense. They're returned. (Technically you can throw them, but this is a bad practice and, for example, you won't write a library that threw exceptions because your caller won't expect it to.)
There's a difference between errors (aka error messages) and exceptions. When an exception happens (i.e. is raised/thrown/etc), the call stack unwinds, and takes down your program, violently (unless you catch the exception). But you already know this, most probably.
Now, in contrast, an error is not something that happens, it doesn't do anything on its own. An error is most of the time a string, and it holds a message about the error.
I'm pointing this out because you're referring to go error types as "exceptions". They are not exceptions. They are a piece of memory with type and value and whatnot. They're variables that can be printed or otherwise reported to the user (or the caller code). That's why they're called error messages, or errors for short.
So, I ask you to refer to errors as errors, and to exceptions as exceptions. And as you have pointed out, go has an exception-like mechanism (but they're used differently), but those are called panics, so those shouldn't be called exceptions either.
About verbosity. Yes, checking errors manually does seem more verbose than relying on exception handling; where you just ignore the the fact that any line of code can potentially throw an exception, and let the caller handle it. If you haven't got used to it yet, then writing all those ifs and returns is a bit tiresome, but they're also readable. Obviously you won't see the difference when writing short scripts, where handling errors is always the same (return until suitable level on call stack, then print it), but when you actually have to write code that handles errors in different ways, then:
you are not forced to handle the error in an error-handling-only codepath (in contrast, try and catch branches enforce exactly this), meaning you can even have multiple errors in flight at the same time (i.e. store them in different variables), and be able to do other stuff before you handle or return the errors. In contrast, when you catch an exception, you have to handle it there and then (or re-throw it).
you don't have to play the "what state gets invalidated when an exception is thrown here" game, because you explicitly see what happens in case of error. Handling returned errors is explicit, while letting exceptions fall-through is implicit. When you're debugging and trying to figure out what code caused your data to be invalid, and you're looking at the source, you don't explicitly see the exception that had caused some code not to run, or had caused some code not to be undone. Especially if you've caught that exception in an upper level than where the data was invalidated.
In concurrent code, it's much harder to use exceptions safely and properly, because if you use it in case of any kind of invalid behaviour, then you have to be very careful that every thread (or goroutine, in go's case) is guarded so that none of them can tear down the whole program, only if explicitly necessary. It's just plain simpler to use error values, with which I can easily control which thread gets torn down and which stays. And even if you write convential go code and don't raise panics, only for truly unrecoverable faults, even then it's pretty tricky to ensure that every thread gets torn down (and by extension, cleaned up), in the correct order, and to ensure that the main thread exits last. Then again, I haven't had the time to figure this out properly, nor have I asked for help on the mailing list (I've run into this issue fairly recently).
There are several arguments for and against both returning errors and exception handling, but I'm not going to discuss all of them here. Some additional food for thought, though:
http://blogs.msdn.com/b/oldnewthing/archive/2004/04/22/11816-aspx
http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx
Go is supposed to force you to check for exceptions (in the same spirit as Java's checked exceptions). Functions can have multiple return values and it is convention to return a tuple of (result, error) where one and only one is non-null. There is also a syntax for destructured assignment.
No, go is not supposed to force you to check for returned errors. What if you have good reasons to ignore a returned error? For example, when I call fmt.Println for debugging purposes, I don't generally care inside the program that it can't access the standard output (which would be very weird, and I as a user of my own computer would have noticed it), in part because it's very unlikely to happen. And so what if it can't access the stdout? It's not that it can write an error message to stdout that it can't access stdout.
For a less silly example, what if I'm processing a big file line-by-line, and one line has weird data in it that I don't care about, because all the other lines are still valid? I can't stop for one measely line that has a misplaced semicolon in it. So I ignore the error. As long as the processing function recognises the invalid data and doesn't corrupt the program state, it's cool. And what if I do report the error? Well, what if all the lines are weird? Then I end up spamming the log or the console. That's bad too. I remember using an mp3 player that did exactly that, spamming the console when it hit a damaged MP3 header.
So, if I don't care about the error, I ignore it. If I do care, and I still don't want to bail out at the sight of a single faulty line, I could stash away the error in, say, an array, and continue. Maybe I decide that I'd bail out after too many erronneous lines, and print out all the (unique) error messages afterwards.
So, it's good that you aren't forced to receive the error return value. If you want to ignore a returned value, you can do this:
result, _ = funcThatReturnsResultAndError()
_ = funcThatOnlyReturnsError()
And for the record, if we have a func that returns two things, then
a = funcReturns2Things()
is a compiler error because you didn't receive the second value, regardless of the value type. It doesn't matter whether the second return value is an error type or a string or an int or whatever. It's illegal, because on the left side you have only one variable, and on the right side, the function returns two values. So you should do this:
a, _ = funcReturns2Things()
or this:
a, b = funcReturns2Things()
or this:
funcReturns2Things()
This is completely unrelated to whether go should force you to check the error values or not. I've never seen it stated or implied that go should force you to do that, and neither the compilers' behaviour hints at it. It doesn't. It shouldn't. There are good reasons for that, see above.
(and actually, one could easily patch the go vet tool to print warnings for ignored error return values, but I don't think it would be very useful. People check their return values.)
Continuing with exceptions, C# exceptions have a string message and a stack trace. Java exceptions have a string message and a stack trace. Go exceptions have a string message. No stack trace. Just the equivalent of a ToString(). So even if you do bubble your "someFunctionThatCouldFail() failed!" exception all the way to your topmost catch-all error handler/logger, it's going to have no idea where the exception actually came from. You could of course put some form of source line information manually in the message, but that still doesn't give you a stack trace of where it was called from. Good luck debugging!
Go errors aren't exceptions. If you want a stacktrace, call the panic function, that's what it is for (and to leave a sinking ship, i.e. unrecoverable bug). But if you're writing a program that is gonna be presented to a user with little to no programming ability (and especially with no incentive to debug your code), then why show them a stacktrace at all? I, for one, am sick of all those python (and sometimes, GTK) programs on my computer that vomit a stacktrace whenever they do something stupid. Despite that I am capable of programming, I, as a user, do not want to debug or maintain other people's code (unless I'm forced to, by job or superior or if all else fails), so I don't want to look at overly verbose stacktraces, especially when a small, concise error message would have done the job.
When you're writing a personal quick script, when you don't care much about correctness or errors? Sure, call panic, and utilize the hopefully rare stacktrace to debug your script. But don't expect the users of your programs to have the enthusiasm to debug your code (even if they have the ability to).
As for tracking down problems based on error messages? Simple, have descriptive error messages, that have some key information, like what data caused the fault to happen (like what file failed to be opened, for example). This is why the error type is an interface: so that anything that has an Error() method can be returned as an error (and have the message printed). Meaning, you can stash away details about the error (like the filename, line number, if it was a parsing error for example) in a struct, and give it an Error() method. Within the calling code, use a type assertion to handle it as the correct type, so that you can access the fields of that struct. So you can both bubble up the error onto the topmost error handler, and still have all the details that you knew when the error happened. For debugging, use strategically placed fmt.Printf calls. Or call panic, if you desperately need a stacktrace.
Remember, in go, error != exception.
Go comes with a few libraries to help you get a basic HTTP server started...
I'm afraid I can't say much about the stdlib's http server, or about handling HTML, for I haven't got much experience with it in go.
When sending a response from an HTTP server...
Skipping this one too, I don't know much about the stdlib http server.
Another touted benefit of Go: You can compile binaries for all platforms from any platform! Except on Windows, it wants gcc to compile for Linux. Oops!
Cross-compiling works for go code only, because in the toolchain, mostly only the go compilers are designed in a way to support sane cross-compiling. It would be non-trivial to get gcc to painlessly cross-compile for every supported platform, out of the box.
Now, any go code that uses cgo (i.e. calls c code) has to use the gcc compiler to compile the C code. The last time I checked, the net package uses cgo, therefore if you're using net or net/http, etc, then you basically can't use cross-compiling, as far as I can tell. The root of the issue is that gcc is a PITA to set up to cross-compile, especially across operating systems where you have to supply the system specific libc header files and whatnot. I tried to cross-compile c code from linux to windows once. It's not a pleasant trip.
But otherwise, this might give you some guidelines, even if you're trying to do it the other way around:
Go has pointers. Go has interfaces. A type T can implement interfaces. A pointer to T, T*, ... can implement interfaces. Any interfaces it wants.
- yes, you can declare a method that has a receiver of type *T, where T is some arbitrary type. What you said about this is true, so far.
Totally unrelated to what are implemented by T.
- yes, the method set of type T and the method set of type *T are different. It is considered bad practice to define methods on both types, so you should stick to only one of them. Read this:
http://golang.org/doc/effectivego.html#pointersvs_values
In fact, this is how the Exception interface works, it's implemented by pointers, not by actual types. That is, if you have a custom exception type MyException, then it's not MyException that implements the Exception class's ToString(), it's MyException*
- This is factually wrong, and confuses some related concepts. You can implement an interface however you like. You can decide whether to use a pointer receiver or a value receiver. I.e. this compiles:
package main
type a int
type b int
func (a a) Error() string {
return ""
}
func (b *b) Error() string {
return ""
}
func main() {
a := a(1)
b := b(2)
var err error
err = a // value receiver
err = &b // pointer receiver
// compile error, because type
// b doesn't have a method Error() string
// with a value receiver.
// err = b
_ = err
}
Related to the above, Go sometimes allows you to write foo.bar where you actually should write (*foo).bar Only sometimes, though. So if you want to be sure, you'd probably write it as the latter all the time just to be consistent.
This is too vague for me to understand. I've been writing go code for some while now, and have written a 2k source lines long go program not so long ago, and I never had to write (*foo).bar, when foo is a pointer. Without anything specific, I can't really give a better answer. I don't really understand what you mean by "only sometimes".
Want to initialize a variable? foo := bar
Want to assign a variable a new value? foo = bar
The compiler knows perfectly well when you assign a value to an uninitialized variable, so this annoying difference is totally cosmetic and pointless.
False. Let's say that we don't need the colon, and a simple = can declare variables:
foo = bar
fooo = bar // typo, becomes declaration
If the language didn't have shadowing, we would end up thinking about local variable names a lot more in order to use descriptive ones. Let's say there's a function called group(), and inside this same function, I want to use a local variable called group (and I don't intend to recursively call the group() function, inside itself). If there were no shadowing, I would have to think up another variable name. Shadowing isn't strictly necessary, but the same is true for good variable names. Or functions. Or structured programming. You can still program pretty much anything using arithmetic, pointers, goto and if statements just fine. Shadowing is convenient, if sometimes a bit confusing.
On the subject of classes, there aren't any. There are structs. Remember how in C, you don't define member functions on a struct? Instead you define them outside and usually put the struct type as the first parameter? That's how it's done in Go too.
False in a couple of details, true in others.
True, no classes. No type hierarchy either, i.e. no inheritance. That's by design, and with good reasons (see this, if you are curious: http://www.youtube.com/watch?v=sln-gJaURzk ). You can still have methods, though, and interfaces, and embedding (which is subtly but significantly different from inheritance).
You are confusing some terms. Let's draw a line between function and method. A function is, well, a thing that is callable, and potentially has parameters and return values. A method is the same as a function, except that it also has a thing called a receiver. Methods could be referred to as member functions I guess (I think that's what they call it in C++).
You can define a method on any type. See the example code I gave above? Those two methods are defined on two distinct int types (well, types that have the underlying type int - it's like a typedef from the c language). It's not possible to define them on the int type directly (because it's a built-in type, it's not local to the package), because that would lead to a huge mess in big programs (think about accidentally redefining methods). You can have your "member functions" on structs too, yes. And you can even define normal functions, of which the first parameter can be a struct, that is true too.
Similarly, Go doesn't have constuctors. The convention to have a constructor for a type Foo is to make a static NewFoo method. Of course, this doesn't prevent the caller from ignoring it and calling new Foo() themselves, so when looking at API docs, always remember to check if a NewFoo method exists or not first! And if you're writing a class, better throw any guarantee that your object will be in a consistent state out of the window!
Also false at places.
True, go doesn't have constructors, because constructors are just an unnecessary special case of functions. And in C++, for example, if you want to avoid using exceptions, but want to handle errors in a constructor, then you'd have to throw an exception, because you can't return values from constructors, because constructors are a special case of functions, implemented as a different language construct. It's just unneeded complexity. Instead in go, you just allocate your data with functions new() and make(). And maybe put them in functions which are called NewFoo and NewBar.
And yes, the convention is that either you create a dedicated function that returns a Foo value, or you don't, if it's not needed, for example if every field of the type is exported (i.e. publicly accessible). Read this:
Of course, this doesn't prevent the caller from ignoring it and calling new Foo() themselves, so when looking at API docs, always remember to check if a NewFoo method exists or not first!
And not entirely true. Let's have these types:
type Foo struct {
nonexported int
Exported int
}
type Bar struct {
nonexported1 string
nonexported2 byte
}
Both Foo and Bar are exported types (they are capitalized), but not every one of their fields are exported (the lowercase initial lettered ones are unexported). Therefore, if you want to use Foo and Bar outside the package they were declared in, you can certainly call
f := new(Foo)
b := new(Bar)
But you can't really access their fields, or not all of them. You can't set their unexported fields from outside the package they're declared in. That means the caller code is pretty limited in what they can do with Foo and Bar. Therefore, it's conventional to provide a function that takes the necessary parameters to create Foo or Bar and return the values, properly parametrized. Your claim about inconsistent state is false. If you don't want a field to be accessed outside the package, don't export it.
Function overloading isn't allowed either. I sure hope you only need one set of parameters for your constructor!
Name your function differently. For example, in the os package there's
http://golang.org/pkg/os/#Create
Each of them can be thought of as a "constructor" (sort of), but all four of them behave differently, take and give different parameteres, and have different names. No need for function overloading. It simplifies the language and speeds up the method lookup when using interfaces, I think.
No generics either.
True. http://golang.org/doc/faq#generics
There's a printf (format string, varargs). There's a println (single string). There's no println that takes a format string and varags, so you have to write "\n" in all of your println's, since 99% of the usages of println are for writing lines.
the uncapitalized print and println functions (without any package qualifier either) are there for bootstrapping purposes. They aren't intended for general usage, they're there to build the go environment more easily. Use the fmt package instead. See these:
Just like the C++ STL, there's no rational thought put behind when something should be a member function and when something should be a static function on the type. So methods like string.Contains are actually static methods on the strings type.
in go, there is no such thing as a static method. There are only functions and methods. Methods are just functions but with a receiver too, not just a regular parameter list and a return value list.
Function example:
func f(a, b int) int {
return a+b
}
a := f(1, 2)
Method example:
type WTF string
func (daily WTF) Post(title string) string {
return fmt.Sprintf("%s\n\n%s\n", title, daily)
}
var lolwut WTF = "herp derp i can haz softcoding"
lolwut.Post("Soft Coding")
The go lang developers were careful not to define any methods on built-in types, by design. That is because it keeps the type system clean and simple. This is why the functions len() and cap() are functions instead of methods.
There exists the strings (<-plural, so it doesnt collide with the type name, written in singular) package, it has functions in it. They're functions mostly, and they operate on the built-in string type, because if they were methods, they'd need to declare a new type that has an underlying type of string. And then in order to use the strings package, you'd have to convert your string into that string.OtherString type before you can call the methods on it. With functions, you don't need to do that.
As for what's a function and what's a method and why, is based on good taste thinking about the implications. There are several reasons why you might want a method instead of a function:
- you want to implement an interface
- you want to put the code in a separate namespace (specific to the type you declare it on)
- you want to couple the code more tightly to the type you declare the method on
- etc. Search the golang-nuts mailing list for more.
Speaking of strings, how do you append a character to a string? First you have to call a method to convert the character to a string. Then you call a method to append the new string to the original string.
Umm.
// let's declare some characters.
ch1 := 'p'
ch2 := 'e'
// declare string
str := "no"
// type conversion necessary.
str += string(ch1) + string(ch2)
"Go doesn't implicitly anything."
Go is supposed to enforce strict whitespacing via compiler errors, and some of them indeed are. Good. (This isn't about the gofmt tool.) But a lot of them aren't. Bad. It's very inconsistent on what it does think is an error and what isn't.
False. The go compiler doesn't enforce whitespace conventions at all, it's not supposed to. It does something related, though, namely, it automatically inserts semicolons into the code where it's appropriate, so that the later compiler passes can parse it. From what I hear, it's much better than the javascript semicolon rule, but I don't use js, so idk. A side-effect of this:
// legal
if true {
fmt.Println("hi mom")
}
// after semicolons inserted:
if true {
fmt.Println("hi mom");
}
// illegal
if true
{
fmt.Println("nopenopenope")
}
// after semicolons inserted:
if true; // semi-colon tumor
{
fmt.Println("nopenopenope");
}
// and this is why the compiler
// would complain about a missing
// true-branch after the if or something
“From the tutorial: "The language forces the brace style to some extent.” Well, that’s it. If I can’t have a brace-war tearing the dev group apart for months and kill productivity, I want nothing to do with that language.“ — SoftwareMaven in hackernews
There are some other deviations from the mainstream C-like syntax, like forcing blocks after if, for, etc; there's no while, no try-catch-finally, and instead defer, panic and recover, etc etc.
Windows is pretty much a third-class citizen right now. For example, a function that takes in a path string and returns the path after resolving all symlinks returns the input path unmodified on Windows. Looking at the source commits, there is a commit that claims to have fixed it but actually didn't.
I wrote a prototype text editor (as in, the actual text control part, and the inmemory editor too) in go, using Go-SDL (an sdl binding for go). SDL is a low-level c graphics/gaming library. The program featured some network code and heavily utilized concurrency (it was a collab text editor prototype, like what Google Docs does).
When I tried building it in windows, I had to do the following: install git (PITA), install hg (hgtortoise was easier to install), install mingw (a gcc package for windows, wasn't too hard to install), install sdl binaries and put them into the proper mingw directories and figure out how how to make pkg-config handle them (major PITA). After all this, I finally installed the go toolchain on windows. This last part was the least painful of all. Then I built the code and started debugging, trying to figure out why the hell the otherwise portable graphics (to restate, written in C) are producing a bug I haven't seen on linux. It took me 2 days to figure out (the fix was related to locking sdl code to a single os thread).
I wouldn't call windows a third-class citizen after this.
Most of your opinions and criticism you have cited here aren't based on facts, and some of them stem from assumptions you've most likely brought along from other languages. I suggest that before you make observations like these, check your facts, check the compiler, and maybe read stuff about go on reddit, hackernews, or the mailing list. Lots of these points were discussed at length already.
I hope I wasn't too offensive or condescending with my answers or my style of giving them.