Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

Given that ref structs can now be generic arguments and cannot be boxed - you have more ways to enforce that no boxing occurs at compile-time. It is true that you have to roll your own collections, but even dispatching on interfaces by making them generic constraints (which is zero-cost) instead of boxing is a good start.

As for delete operator, 'dispose' works well enough. I have a toy native vector that I use for all sorts of one-off tasks:

  // A is a shorthand for default allocator, a thin wrapper on top of malloc/realloc/free
  // this allows for Zig-style allocator specialization
  using var nums = (NVec<int, A>)[1, 2, 3, 4];
  nums.Add(5);
  ...
  // underlying pointer is freed at the end of the scope
It is very easy to implement and I assume C and C++ developers would feel right at home, except with better UX.

This retains full compatibility with the standard library through interfaces and being convertible to Span<T>, which almost everything accepts nowadays.

System-provided allocators are slower at small allocations than GC, but Jemalloc easily fixes that.



> Given that ref structs can now be generic arguments

I missed this development! That was a big pain working with ref structs when they first came out.


Ref-structs can also implement interfaces now too. The C# compiler team has been really delivering in this space the last few iterations


I really mean using existing stuff, without rolling your own:

List<int> nums = [1, 2, 3, 4];

//do stuff with nums

Delete(nums);


Okay, I see where you are coming from. This is a common ask, but it works against the principles that make generational GCs performant. You can't "delete" an object from the heap, because dead objects are not deallocated. Instead, live objects are preserved and moved to an older generation, with memory now occupied by only dead objects made available for subsequent allocations immediately.

In addition, objects that hold references to other objects internally would need an implementation that would allow to traverse and recursively free references in a statically understood way. This gets nasty quick since a List<T> can hold, let's say, strings, which may or may not have other locations referring to them. Memory safety goes out of the window for dubious performance wins (not even necessarily, since this is where GC has better throughput).

I can recommend watching the lectures from Konrad Kokosa that go into the detail how .NET's GC works: https://www.youtube.com/watch?v=8i1Nv7wGsjk&list=PLpUkQYy-K8...


> Okay, I see where you are coming from. This is a common ask, but it works against the principles that make generational GCs performant.

In my comment I already suggested a context where GC can be turned off. I said: "It would be better if the GC can be turned off with a switch and just add a delete operator to manually free memory."


And that'd totally break down as soon as some underlying class does something you didn't expect. C++ RAII patterns and Rust's ownership systems are required for a very good reason (that the GC sidesteps but also makes all code dependent of), the NVec further up in the thread works because it's an explicit abstraction.


Use the stuff from Marshal and OS interop then, there are even malloc/free variants.

Also there is C++ for that, if the goal is to use C# as C++.


This looks intriguing. Is there anywhere I could see more details about this?


https://github.com/neon-sunset/project-anvil/blob/master/Sou...

This really is a PoC. You might get better results by using snippets as the inspiration for rolling something tailored to your specific use-case.


Thank you! It'll be fine learning resource.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: