First of all, I just want to say that I don't think that Haskell or Rust are competing with Go, we're just talking type systems here. I'll use those two because they're my favorite. Three examples, one from each and one they share:

In Haskell, the type system ensures that side effects only happen in one place: things inside a monad of some kind. For example, if I'm given a function:

foo :: Int -> Int

I _know_ for a fact that this function doesn't do any IO. It doesn't maintain any state. It won't launch the missiles. And I also know that everything needed to understand what goes on in `foo` will happen via the one parameter. Because it's annoying to pass around code that interacts with the outside world, you end up with a small shell of imperative, stateful code, and a large amount of stateless, pure functional code. Since Go (to my knowledge) doesn't enforce referential transparency, it won't do this.

Haskell and Rust both don't have the concept of null. This is fantastic, as even the inventor of null thinks it's a bad idea. It's borderline irresponsible to write a new programming language with nulls today. So how do they handle a computation that may fail? Higher order types:

use std::option::Option;

fn call_me_maybe(x: int) -> Option<int> {
if(x > 5) {
Some(x)
} else {
None
}
}

fn main() {
let i = 6;
match call_me_maybe(i) {
Some(x) => println!("Yes! {:i}", x),
None => println!("nope"),
}
}

This will print "Yes! 6". If you change it to 5, it will print "nope". The higher type wraps the output and tell us if we've succeeded or failed. Here's the kicker: what happens if we leave off the error case?

rust.rs:13:8: 15:9 error: non-exhaustive patterns: None not covered
rust.rs:13 match call_me_maybe(i) {
rust.rs:14 Some(x) => println!("Yes! {:i}", x),
rust.rs:15 }

It'll make us handle it. And since it has a different type, we can't pass it to something that takes an `int` either, because it's an `Option<int>`. Both Rust and Haskell can do this, and have tools to make this easier, too. For example, maybe you want an error message, so Rust's Result type has a message on the fail case. Or you need three or more states, you can build your own.

Finally, in Rust, data is immutable by default:

let i = 6;
i = i + 5;

rust.rs:3:8: 3:9 error: re-assignment of immutable variable `i`
rust.rs:3 i = i + 5;
^
rust.rs:2:12: 2:13 note: prior assignment occurs here
rust.rs:2 let i = 6;

And pointers have explicitly one owner or many owners:

fn main() {
let i: ~int = ~6; // read: i is an owned pointer to an int.
let j: @int = @6; // read: j is an managed pointer to an int.
let k = i;
let l = j;
println(i.to_str()); // error: use of moved value: `i`
println(j.to_str()); // this is fine
}

So in Rust, you cannot share data that has multiple owners across threads:

fn main() {
let i: ~int = ~6; // read: i is an owned pointer to an int.
let j: @int = @6; // read: j is an managed pointer to an int.

do spawn {
println(i.to_str()); // fine, prints
}
println(i.to_str()); // you can't use it after you've given it away, either!
// error: use of moved value: `i`
// println(i.to_str()); //
// ^
//note: `i` moved into closure environment here because it has type `~fn:Send()`, which is non-copyable (perhaps you meant to use clone()?)
//do spawn {
// println(i.to_str()); // fine, prints
//}

do spawn {
println(j.to_str()); // error: cannot capture variable of
// type `@int`, which does not fulfill
// `Send`, in a bounded closure
}
}

Therefore, race conditions are a compile-time error. Go has a race detector, which can help, but it can't always help.

Anyway, hopefully that explains some of my beef with Go's types. I think Go gets a lot of things right, I think the type system is one place where it gets things really wrong.