???
By the time it gets to the JIT, java generics have long since been erased.
Java's generic classes run at the same speed as everything else, because there's no actual generics there.
(Although I'd be very surprised if this doesn't change in the near future: https://openjdk.org/jeps/8261529.)
Five years from now, Go will still exist. The implementation of generics in that future version of Go is likely to be much faster and better than what we currently have.
Put a different way, "Generics are slow in Go v1.18"
I would rather call it cherry-picking if one avoided covering such a case to make Java look good. Seems like a pretty obvious problem to me.
Handling basic number types is pretty bread and butter.
Java is way worse than Go on that aspect.
The reasons are explained here: https://go.googlesource.com/proposal/+/refs/heads/master/des... but the point is, you can have this in Java, but not in Go.
- func (self SomeType[T]) Foo(...) is a method for a class parametrized on T. So if you want foo.Foo(1) then it only works for foo that's SomeType[int]. You cannot then easily call foo.Foo("one") on the same instance.
- func (self SomeType) Foo[T any](...) - if that'd be a thing - would be a generic method on SomeType that works for any T. So you can foo.Foo(1) and foo.Foo("one") on the very next line and that would work.
However, it won't work for return values, right? Because e.g. `func (some something[T]) hi(b T) T` would return `any` and not whatever it was provided (`int` or `string` respectively).
https://go.dev/play/p/IYoPUQg04sg
Also, I don't think this can work well with types narrower/fancier than `any`, e.g. I had trouble figuring out how to deal with
type IntOrString interface {
int | string
}
type something[T IntOrString] int
https://go.dev/play/p/acwjxdpHTJOThis is extremely important, because it means `func (someType[T any]) foo(T x) T`, `x.foo(1)` would return `any`, not `int` like a generic function would.
In fact, if you're going to instantiate a generic type parameter with `any`, I would very much doubt you wanted a generic type in the first place.
You look at something like the Reader and Writer interfaces and there are very few methods. The Go approach is to put more functionality in free functions such as fmt.Fprintf which allows more code reuse across multiple types.
In fact I view the heavy Java use of methods as an anti-pattern.
But for some `foo.Add(bar)` can be considered nicer than `AddFoo(foo, bar)` or `foopkg.Add(foo, bar)`. The semantic differences are mostly in in an aesthetic/stylistic/personal preferences realm, so it's hard to argue for or against it.
And either way, I'd disagree on the general principle. I'm not claiming to be well-versed in Go styles and patterns, but most well-designed libraries I've seen had exposed `foopkg.NewFoo()`, useful structs and constants (e.g. `foopkg.FooParams` or `foopkg.NoSuchFoo`) and the rest is typically interface methods.
So I guess our experiences differ, which of course is fine, but I respectfully don’t concede the point. :-)
Of course, type erasure isn't the actual culprit in that case, but once you don't do it, it gets very tempting to allow pattern matching on type parameters...
And people wonder why we complain about C++..
So this excuses the awful Java genetic how? "It smells like sewage in the basement because the basement is full of sewage." In addition, generics over primitive/stack/value types (including mathematical operators) is working just fine in the latest iteration of C#.
I'm personally no fan of Go (mostly due to the ergonomics of the standard library), but all this about generics is throwing the baby out with the bathwater. Go has a fantastic generics design.
> cost/investment ratio in Go is really good for writing server-side stuff.
And this 100%.
I just did that - I've been working on a very similar thing to goduplicator (a mirroring proxy), however with an additional requirement that it must not add latency to the primary communication path, e.g must connect mirrors asynchronously and buffer data instead of waiting for a slow mirror.
I chose Rust and the resulting code has been at the same time simpler, faster and uses 3x fewer memory than the Go implementation. I have not only select!, but also things like join! or try_join! at my disposal, way simpler to use than channels and waitgroups.
Also looks like closing connections in Go is a mess. In Rust I just remove a connection value from the vector and I'm done. In Go they have to close them manually, but defer is quite useless in async code, where the connections are passed to a coroutine.
In Rust I can also interleave work concurrently on a single thread with async, avoiding synchronisation. In Go I'd have to spawn goroutines and then coordinate them through channels which would be both more complex, more costly to execute and more risky.
So IMHO Go is definitely an interesting language to write concurrent networking code in, but feels quite incomplete to me. You get products but no sum types, you get select (which is kinda sum for channels) but not join (which is an analogy of a product for channels). You get semi-automatic cleanup with defer, but it is tied to a lexical scope ignoring the fact that goroutines can outlive it.
GC is only a minor reason I chose Rust over Go for a networking related project. Despite having a GC, Go definitely feels more low-level and less structured than Rust, and leads to code that is longer and harder to reason about.
It is very similar to how a language with only a goto instruction to do control flow would be definitely simpler/smaller than a language that supports functions, loops and conditions, but the actual programs written in it would be brittle and harder to understand.
That is going to depend entirely on requirements. Fast delivery, fast execution, correctness and others. No tool for everything :)
If generics had had to actually support ArrayList<int>, they would have required significant changes to how ArrayList is stored in memory, and to how a lot functions which work with generic types are actually compiled. Instead, Java has the easy path in the implementation: since everything only works with reference types, there's no need for multiple copies of a function to work with different types.
Of course, this means that sum<Integer>(new Integer[1000000]) is going to be orders of magnitude slower than sumInts(new int[1000000]).