Having written in both Go and Rust, I can see the arguments. Go has a relatively simple, easy to use type system. The problem is that whenever you need to do anything generic, you have to use Go's "any" type, "interface{}", and the reflection system. This results in doing stuff over and over at run time that could have been done once, preferably at compile time. For much web back-end stuff, though, you don't need a really fancy type system with generics. Mostly you're pushing strings around, which Go does quite well.
Rust's type system has most of the bells and whistles of C++, plus some new ones. It's clever, well thought out, sound, and bulky. Rust has very powerful compile-time programming; there's a regular expression compiler that runs at compile time. I'm concerned that Rust is starting out at the cruft level it took C++ 20 years to achieve. I shudder to think of what things will be like once the Boost crowd discovers Rust.
The lack of exception handing in Rust forces program design into a form where many functions return "Result" or "Some", which are generic enumeration/variant record types. These must be instantiated with the actual return type. As a result, a rather high percentage of functions in Rust seem to involve generics. There are some rather tortured functional programming forms used to handle errors, such as ".and_then(lambda)". Doing N things in succession, each of which can generate an error, is either verbose (match statement) or obscure ("and_then()"). You get to pick. Or you can just use ".unwrap()", which extracts the value from a Some form and makes a failure fatal. It's tempting to over-use that.
The lack of exception handling in Go yields too many "goto" statements. There's also a tendency to turn the panic/recover mechanism into an exception system. This has roughly the problems of C's" longjmp".
As a practical matter, Go now has most of the libraries you need for server-side programs. (Not GUI programs, though.) Rust has only very basic libraries, and they're not stable yet. The language hasn't quite settled down. People are writing and porting libraries at a good pace, and this problem will be solved.
> I'm concerned that Rust is starting out at the cruft level it took C++ 20 years to achieve.
That is the core reason why there are no generics in Go. Not because the Go team thinks they're a terrible idea, but because they think cruft is a terrible idea.
The default answer to a new feature is "no". When everything seems to be going fine without generics (except for internet flamewars), the answer stays "no".
The reason there are no generics in Go is because nobody has implemented them in Go (or presented a concrete proposal for implementing them, i.e. something less vague than "just do what C# did" while ignoring that C# is not AOT compiled). Thre are some monomorphizing preprocessors but they tend to punt on the interaction betweeen generics and the rest of the language.
I find the community's insistence that Go does not have them because they somehow make programming worse to be fairly ludicrous, when there are much more practical reasons. Obviously you can write useful code without generics (C does not have them), but that does not make them useless, or "cruft", or an undesired feature (unless you simply choose to believe that every single person complaining about them does not use Go): you can also write useful code without many other features that Go does have.
Does this take significantly longer to compile than regular C# code (I can't imagine the answer is no)? If so, it doesn't really address the concerns Go's implementors brought up, which precisely revolved around the compile time : runtime performance tradeoffs that the JIT compiler helps mitigate for C#'s generics.
Idiomatic Rust uses try!. It is a less verbose version of the most common pattern of error handling in Go. You have not programmed very much Rust and are not yet familiar with its idioms, but that is by far the most common and avoids all the problems you just described.
"try!" is close to something one might call "cruft". It tests a function for an error value, and if it gets one, it returns from the containing function. "try!()" is written like a function call, but it doesn't work like one - it can execute a return statement. That's outside the normal semantics of the language. This is the only macro in the standard macros with such semantics. Fortunately. This is a capability which needs to be used with extreme restraint. Writing a macro "try_except_finally!(maincode, exceptioncode, cleanupcode)" would probably be a bad idea.
There was at one time a fad for extending C with macros like that. Fortunately for program readability, it didn't last.
`throw` in Java's exception handling also prematurely exits a function. So?
While `try!()` is a macro and not a core language feature, most of the macros in the standard library can be treated as such. So a Rust programmer will know that `try!()` can return, just like a Java programmer knows that `throw` returns early.
Besides, macros are syntactically distinguishable in Rust. When you see that exclamation mark, you have to rememeber that it's a macro and might be doing arbitrary compile time things along with arbitrary token tree expansion.
Except that in Rust, all macros are syntactically distinguished from non-macros (as well as hygienic, unlike C macros), and try! is extremely common (so the claim that people won't know what it does is questionable). I agree that macros like this should be carefully considered, but I think experience has already shown us that try! is perfectly fine.
Rust's type system has most of the bells and whistles of C++, plus some new ones. It's clever, well thought out, sound, and bulky. Rust has very powerful compile-time programming; there's a regular expression compiler that runs at compile time. I'm concerned that Rust is starting out at the cruft level it took C++ 20 years to achieve. I shudder to think of what things will be like once the Boost crowd discovers Rust.
The lack of exception handing in Rust forces program design into a form where many functions return "Result" or "Some", which are generic enumeration/variant record types. These must be instantiated with the actual return type. As a result, a rather high percentage of functions in Rust seem to involve generics. There are some rather tortured functional programming forms used to handle errors, such as ".and_then(lambda)". Doing N things in succession, each of which can generate an error, is either verbose (match statement) or obscure ("and_then()"). You get to pick. Or you can just use ".unwrap()", which extracts the value from a Some form and makes a failure fatal. It's tempting to over-use that.
The lack of exception handling in Go yields too many "goto" statements. There's also a tendency to turn the panic/recover mechanism into an exception system. This has roughly the problems of C's" longjmp".
As a practical matter, Go now has most of the libraries you need for server-side programs. (Not GUI programs, though.) Rust has only very basic libraries, and they're not stable yet. The language hasn't quite settled down. People are writing and porting libraries at a good pace, and this problem will be solved.