4 ms·
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 seam
by consteval 2y ago
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.
> you can easily pass structs by reference via ref, out and in keywords. On top of that, ref structs can hold `byref` pointers aka 'ref' keyword which can point to arbitrary memory
These are not features I've encountered. I wonder how you solve dangling references when those references could point to automatic stack variables.
- neonsunset 2y ago> 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<int, string>` has a generic instantiation indicated as `Dictionary<int, __Canon>` 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<T>` 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-reference/keywords/ref 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 :)
- afdbcreid 2y ago> I wonder how you solve dangling references when those references could point to automatic stack variables. By restricting them, to, essentially, disallow storing `ref`s on the heap.