> Wow, I was not aware of the monomorphization of structs in C#. That's very interesting, I wonder how you're able to mix generic structs and generic classes seamlessly.
The handling is transparent, in a way. As implemented by CoreCLR, class-type generic arguments have shared representation named __Canon. This means that, for example, a `Dictionary` has a generic instantiation indicated as `Dictionary` where __Canon is an implicit generic type argument passed alongside relevant calls. Statics referencing that do get exact address, and there is quite a bit of complexity regarding runtime handling of this as far as virtual calls and other edge cases are involved, but as a programmer you are never exposed to that directly. It's an implementation detail, and even un-monomorphized cases work rather fast in most situations, like standard data containers.
> These are not features I've encountered. I wonder how you solve dangling references when those references could point to automatic stack variables.
Not sure what you mean by automatic stack variables, but the idea behind byref pointers\managed references\'ref's is that they are not allowed to be boxed or otherwise placed on the heap.
This lifetime restriction enables key scenarios:
- byrefs can point to object interiors without hindering GC throughput
- byrefs can point to stack memory, allowing `var span = (stackalloc byte[32]);` and more
- byrefs can point to any unmanaged memory without requiring excessive range checks by GC
This way you can use `ref T` to represent any memory location and `Span` to represent any contiguous memory range up to 2B elements, without having to carry around bespoke overloads and types that disambiguate between containers and memory sources, much like you would usually see in most other GC-based languages.
There is also additional lifetime analysis in Roslyn to prevent you from returning 'scoped' 'ref's to an outer scope, like having a ref point to an integer in the current method body and returning it to the caller - this will not compile, unless you override it with unsafe [UnscopedRef] which you should never do unless you are absolutely certain (every time I used it and was absolutely certain, the compiler was right and I was not :D). This also works "through" ref structs and other tricky scenarios, which means you can return a ref that points to a middle of heap-allocated array - the scope of object exceeds current method, and byref can keep the array rooted even if no other reference to it exists. The main restriction is byrefs can mostly flow "downward" as not to escape the scope they originate from, but that's a given in most scenarios in Rust just as much.
There is a basic walkthrough about byrefs here: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
C# is good at systems programming - it also has portable SIMD and intrinsics, static linking, native compilation and zero-cost FFI. Engineers getting surprised by this fact rather than upset is the happier but unfortunately less frequent outcome :)