Maybe, but Java programmer would have a lot of issues the moment they go beyond regular LOB application code. Or even when they stay there, the expectation of "it's another Java" would lead to them encountering a lot of surprises. They would also use C# completely ineffectively if they would need to write performance-oriented code. Compared to that, switching from C# to Rust feels like a systems programming focused upgrade, with more than half of the moving parts being familiar the moment you get used to the syntax and understand how they map to C# concepts.

And no, C# has generics, like in Rust, while Java has "generics". Generic arguments of class type in .NET result in method body sharing, yes, and represented in the generic signature as __Canon, this is quite close to dispatching by Box<dyn Trait> in Rust. On the other hand, struct generics in C# and struct generics in Rust have identical behavior codegen-wise - they are monomorphized. CoreLib and many other high-performance libraries heavily leverage this fact to make their internal and external abstractions zero-cost. For example System.Numerics.Tensors which is pure C# BLAS package: https://github.com/dotnet/runtime/blob/main/src/libraries/Sy... combines two operators that are then statically dispatched as it's no different than using traits in Rust save for less convenient syntax.

The biggest drawback to .NET generics is that C# type system is not full Hindley-Miller + ability to specify associated types, so the generic signatures can end up being clunky. But this is not an area where work is done and future C# version will see improvements to generic type inference to reduce effort to write zero-cost abstractions and improve simplicity of generic composition.

I find the mention of .NET origins as an argument unfortunate, because events that happened more than 20 years ago have no relevance whatsoever to what .NET (the non-Framework one) is today.

Async mechanism is closer than you might think as Rust futures are, in a way, state machine step groups, while C# tasks are a more granular and lightweight version of Rust tasks. ValueTasks blur the line slightly due to conditional state machine box allocation. In either case this will lose relevance once runtime handled tasks project is done in one of the future versions which will change the implementation details once more.

In general, if you think that low-level and performance-oriented features are comparable with Java, I'd like to ask you to look into this again. Unsafe syntax in Rust allows relaxed memory safety techniques and enables additional syntax, C# is very similar, there are differences, sure, but the latter too exposes full-blown C syntax for pointers, structs with explicit layout and fixed buffers, safe and unsafe variants of stackalloc (Rust lacks it out of box, heh) and more. It's not just pointer syntax. I could also mention FFI and how the amount of boilerplate even with the Panama project is still laughable.

Writing interop for either of the two is quite comparable - declare exports/imports and map language-native types to FFI-safe, or pass them as is as if they are already, with the calls being more or less direct, possibly statically linked. Not the case with Java.

C# extension methods are Rust associated functions.

Also, C# has Kotlin-style nullability, but nullable structs (e.g. int?) are a sugar for Nullable<T> which is similar to Rust's Option<T>, and can be pattern matched (nullable reference types too, but because this was not built into the language at its inception, there is this divide between nullable reference and value types when it comes to type system representation, particularly felt around generics, which is unfortunate).