> However, such optimization is theoretically possible. The stack is definitely faster than anything else.
What you're describing isn't a stack, but an automatic arena, and this optimisation is easier to do in Java. It's easier to do in Java because it requires setting a "current arena" or inlining, both of which Java can do more easily, and then either the arena will be heap allocated (which will be slower in Rust) or associated with the thread, which is not something low-level languages tend to do.
> You don't need GC if you allocate data on stack. You also do not need to dereference the pointer.
Moving collectors don't need to dereference anything (they don't know and don't want to know when an object is "dead"), and stack allocation works in both languages, only, as you pointed out, is not quite general (not every data structure with a known lifetime can be allocated on the stack).
> You mentioned templates. In Rust, traits that are monomorphized - much like templates-are the standard approach; using vtables or `dyn trait` is a relatively rare use case. This stems from the fact that all code is known at compile time and there is no dynamic loading, allowing the compiler to eliminate polymorphism from the code entirely.
Sure, except Java does this automatically, and it can do it more aggressively. Dynamic dispatch is rare in low-level languages because it's expensive in those languages. But it's not easy to avoid as programs get larger. That is exactly one of the problems in large programs that the JVM set out to solve.
> This might be considered convenient, but in my view, it also leads to code that is harder to maintain when objects can be modified from multiple places. However, I think that is outside the scope of the current discussion.
I agree that whether it has downsides is outside the scope of this discussion, but the point is that as programs evolve and grow, the abstractions tend to be more general, and low-level languages suffer from "abstraction cost", where the more general abstraction (which becomes more common over time) is more expensive. Again, this is exactly why large C++ programs suffered from performance issues and what the JVM tried to address.
> Yes, monomorphization is the default solution in Rust.
... and in C++. But it is viral, and Java monomorphises without suffering from "zero overhead abstractions".
The ability to move pointers, both to data and to code, opens up the possibility of using JITs and moving GCs, which are very powerful optimisations. A JIT does impose two further tradeoffs (aside from the need for an FFI layer), though, which are warmup and the possibility of deoptimisation. We can now cache the generated machine code from one execution to another (https://openjdk.org/jeps/544), but the possibility of deoptimisation remains (in fact, it's what enables the aggressive speculative optimisations), which means you gain average (or even amortised) performance at the cost of the worst case.
Anyway, the JVM was designed as a solution for the performance issues low-level languages suffer from as programs grow and/or evolve. It comes with tradeoffs, but those most affect small or short-lived programs.
The thing to remember is that low-level languages are not optimised for performance but for low-level control (i.e. pointers are direct addresses etc.). Such control can translate to good performance when programs are small (see next) but it becomes a practical hindrance to performance when they're large.
> I suppose you could say that the programming style I am talking about is complex, inconvenient, unmaintainable, and so on. What I mean is, assuming this programming style is sufficiently convenient—and perhaps even has its own advantages
That advantage is a performance advantage. The question isn't "does there exist (in the mathematical sense) some program that is fast?" but "how fast is the program we can write within the budget we have?" When programs are small, manual optimisation is practical; when they grow large - not so much. And that's excluding the matter of a moving collector, which is just hard to compete with on speed regardless of program size, unless you use areans, but they're not at all easy to use in most low-level languages except Zig.
> and the Rust code will definitely be faster.
This is true only in the abstract mathematical sense. The reason we don't write programs that we want to be fast in Assembly (which is faster than anything in the same sense: for any program in any language, there exists and Assembly program that's at least as fast) is not because other languages are fast enough, but because in practice the programs we can actually write in the budget we have will be faster than the Assembly programs we could write. Of course, that could change when AI is able to generate perfect low-level code, but when that happens, it might as well generate machine code directly.