There's no operator overloading, macros, or monkey patching, so you don't have to know about the entire state of the universe to know what one little function will do. There's no destructors or finalizers that magically get called sometime in the future.
There's no exceptions, so you never have to worry that the control flow of your program is going to jump all over the place. There's multiple returns, so you almost never have null pointer exceptions, because you always just check the error before using the value returned.
And of course, channels and goroutines make modern multithreaded code very easy to reason about, because they're just functions and a little pipe primitive. They don't do a lot of fancy schmancy stuff, and that's good. It means it's very easy to tell what go Foo() will do.
There's probably more I can't think of right now.
In what way? It's not a very featureful language, but that doesn't necessarily mean that using it is simpler.
It's certainly not simple to set up! Most languages, I can put my projects wherever I want. It took me a while to find out that I had to put my projects in my Gopath for them to properly compile with third party libraries. There are lots of weird restrictions like that.
>Everything is just pass by value.
Not entirely true. Goroutine capture is by reference, which is why a lot of new people have trouble with goroutines in for loops.
>There's no operator overloading, macros, or monkey patching, so you don't have to know about the entire state of the universe to know what one little function will do.
True, those things are gone, but the things that cause confusion most of the time are still very much present. Go, like most imperative languages, can suffer from global variable overload. Lots of Go code is also so badly typed that it's impossible to tell what things are supposed to be. I can't tell you how many times I've seen `x := poorlyNamedFunctionWTFIsThis()` or `foo(x {}interface){... x.bar()}`
>There's no exceptions
There are `panic()`s.
> There's multiple returns, so you almost never have null pointer exceptions
Rust also has multiple return (because Rust returns via hidden out-param), so I wouldn't really say this makes Go particularly competitive.
Rust also doesn't even have null pointers at all, at least in `safe` code blocks. Rust uses `Option` instead. (Like I said, lots of good features from Haskell/ML.)
>channels and goroutines make modern multithreaded code very easy to reason about
I agree, Go does this well, but let's be fair; a lot of modern languages have primitives like this.
> Rust also has multiple return (because Rust returns via
> hidden out-param)
Not quite. Rust does have multiple returns, but that's just the obvious result of supporting tuples in the language (to say nothing of tagged unions). The hidden out-param you speak of is just there to guarantee return-value optimization, allowing the compiler to construct return values in higher stack frames to avoid the overhead of copying.Yes, but it's (from a user standpoint) practically the same thing!
>The hidden out-param you speak of is just there to guarantee return-value optimization
Yes, which allows for the efficient return of arbitrary-sized structs, i.e. multiple return.
In the ways I described below that line :)
> It's certainly not simple to set up! Most languages, I can put my projects wherever I want. It took me a while to find out that I had to put my projects in my Gopath for them to properly compile with third party libraries. There are lots of weird restrictions like that.
Python, C, and C++ all have magic directories just like that. For ex /usr/local/lib/python2.7/site-packages/
At least Go lets you define where to put it yourself, and the paths are just the URL for the VCS.
> Goroutine capture is by reference, which is why a lot of new people have trouble with goroutines in for loops.
I think you're talking about what happens when you run closures as goroutines in a loop, like this: http://play.golang.org/p/1NX39zk4NI
This is just a misunderstanding of how closures work and what it means to start a goroutine. Yes, closures capture the variables so that modifications inside the closure and outside the closure both affect each other, that's the whole point of closures. Goroutines, when started, may not start right away, and actually generally don't, certainly not faster than this loop can run.
> Go, like most imperative languages, can suffer from global variable overload. Lots of Go code is also so badly typed that it's impossible to tell what things are supposed to be. I can't tell you how many times I've seen `x := poorlyNamedFunctionWTFIsThis()` or `foo(x {}interface){... x.bar()}`
You can write bad code in any language. I don't think that's an indictment of go.
> There are `panic()`s.
Panic is intentionally clumsy and is actively discouraged. Any package that panics as a form of replacement for exceptions will basically never get used by the community. That's a big difference from other major languages that use exceptions for error propagation and control flow.
> Rust also doesn't even have null pointers at all, at least in `safe` code blocks. Rust uses `Option` instead.
From what I've read this is actually just inferior to Go's multiple returns, because options don't tell you why they failed, unless you use multiple returns, in which case it's basically exactly like Go.
Go:
val, err := foo()
if err != nil {
return err
}
Rust: let (opt, err) = foo()
match (opt) {
(None) => return err
}
Is that really any better? In Rust you have to know what match does, what this magic (None) => business is... whereas the Go code just reads like normal C-ish code. Anyone who's ever taken intro to programming would be able to tell you what the Go code does. To understand the Rust code, you have to know a lot of the language internals. And that's exactly what I mean about Go code being simple and easy to understand.> a lot of modern languages have [concurrency] primitives like this.
Not any of the mainstream languages. C, C++, Python, Ruby, C#, Java, Javascript .... some of them can do similar things, but it takes a lot more boilerplate and it's a lot uglier and harder to reason about.
[Edited for clarity]
> options don't tell you why they failed
Options aren't built into the language, they're just a standard library type that any user could define themselves, and we don't recommend them as a replacement for robust error handling anyway. For that we have the Result type, another type that's defined in the standard library, which does indeed contain an error message. > you have to know what match does,
> what this magic (None) => business is...
`match` is just a beefed-up switch statement. And `None` is, to reiterate, an essential component of the standard library, and not magical in any way.Your Rust code also shows that you've never actually used it before. Here's a proper translation of the Go code into Rust:
let result = foo();
if result.is_err() {
return result;
}