digitalmars.D - Future of SafeRefCounted
- Dukc (64/64) Feb 22 `SafeRefCounted` depends on DIP1000 to work. Because of that, it
- Dukc (3/5) Feb 22 Correction: Unit tests do pass, but the test suite as whole gives
- Dukc (35/36) Feb 23 Wow, how did I write about modules all the way down without
- Dennis (9/16) Feb 23 To me it always looked hacky, and I'd get rid of it as well.
- Dukc (18/22) Feb 24 I disagree, reference counting is a great tool for this use case.
- Sebastiaan Koppe (5/16) Feb 24 For resource management with deterministic lifecycle, I usually
- Adam Wilson (5/8) Feb 25 In general I approve of using arena allocators. RC is useful, but
- Jonathan M Davis (37/38) Feb 24 Personally, I'm very much hoping that DIP 1000 gets killed off. There ar...
- Richard (Rikki) Andrew Cattermole (10/29) Feb 24 I'm exploring it from both directions.
- Sebastiaan Koppe (17/24) Feb 25 I use dip1000 in two ways, firstly to avoid allocations on the
- Dukc (15/19) Feb 25 I had another look at it, and my conclusion is, it actually
- Derek Fawcus (11/16) Mar 05 How does this compare with a similar mechanism in C#, which seems
- Sebastiaan Koppe (4/8) Mar 05 Yes indeed. This goes to show dip1000's aim is the right one,
- Derek Fawcus (5/13) Mar 06 Here is another article which touches on the same areas, and may
- Dukc (37/49) Mar 06 DIP25 is pretty similar to this, except it can also specify
`SafeRefCounted` depends on DIP1000 to work. Because of that, it was requested early in the PR review adding it that it should only be ` safe` when DIP1000 is on, not otherwise. This sounded obviously a good idea back then, so the destructor of SafeRefCounted has a `static if` that checks for DIP1000 and does the deallocation inside a ` trusted` lambda if the switch is enabled, directly otherwise. This makes the destructor ` system` for non-DIP1000 client code, ` safe` for DIP1000 code. But, I've come to think it'd have been better to leave it ` safe` for all code. Have you looked at [std.dirEntries](https://dlang.org/phobos/std_file.html#dirEntries) recently? It has a compile-time template parameter called `useDIP1000`, that is always set automatically and is even checked with a static assert to be set to be default option. Isn't this crazy? But believe or not, the unit tests don't pass without the template parameter! The issue is, the return value of `DirEntries` is implemented with SafeRefCounted. This means it, like `SafeRefCounted` itself, is safe iff DIP1000 is on. Without the `useDIP1000` flag, the linker can attempt linking to non-DIP1000 `dirEntries` from `dip1000` client code or vice-versa, causing an ABI mismatch since the safety of the function affects it's name mangling. Imagine some average user trying to define their own type with `SafeRefCounted` and getting linker errors like that. When I encountered the linker errors, I knew that I had just made `SafeRefCounter` safety to depend on the DIP1000 flag, but it was still very tricky to figure out what was happening and how to solve it. We certainly can't expect the same from users. Now, maybe this would let non-DIP1000 users to write unsafe code in ` safe`. I'm not actually sure if that's the case, but assuming it is I still think it would be the lesser evil here. Remember, ` safe` has pointer escaping holes in non-DIP1000 code anyway. I don't see how the possibility of the `std.typecons.borrow` argument function escaping the reference to the payload is any different from the possibility of leaking a slice of a static array in the stack. ---------------------------- Which leads to another issue: language modules and Phobos 3. My understanding is the current plan with Phobos 3 is to not use DIP1000 in it. `SafeRefCounted` depends on DIP1000. Without it it isn't any better than the old `RefCounted` type. Surprisingly, this doesn't automatically mean it can't be done in Phobos 3. Within the present language it wouldn't be possible, but modules are going to change the calculus. In the future when DIP1000 is on or off per module as opposed to compilation unit, it is going to the client code which has to be DIP1000 for ref counting to be safe. The reference counted type can be implemented with our without DIP1000 just as well (although non-DIP1000 implementation of `borrow` would probably need a [currently missing](https://github.com/dlang/dmd/issues/20302) ability to manually inspect the scopeness of the argument function - currently the safety is inferred by calling the argument function with a `scope` argument which probably doesn't work outside DIP1000. Also ` safe`ty unit tests of it would need DIP1000.). Modules are also one more reason to do away with inspecting for DIP1000. With modules, the current definition of the inspection ```D private auto dip1000Test(int x) {return *&x;} // Used in the `static if` package(std) enum dip1000Enabled = is(typeof(&dip1000Test) : int function(int) safe); ``` is going to inspect Phobos, not the client code. While we're probably going to have a way to inspect client code instead, IMO it is a code smell if you're inspecting the module / completion flags of your client code. What are your thoughts?
Feb 22
On Saturday, 22 February 2025 at 19:05:49 UTC, Dukc wrote:But believe or not, the unit tests don't pass without the template parameter!Correction: Unit tests do pass, but the test suite as whole gives a linker error in apparently unrelated place.
Feb 22
On Saturday, 22 February 2025 at 19:05:49 UTC, Dukc wrote:Which leads to another issue: language modules and Phobos 3.Wow, how did I write about modules all the way down without noticing? I meant *editions* in all but one instance. So: Which leads to another issue: language editions and Phobos 3. My understanding is the current plan with Phobos 3 is to not use DIP1000 in it. `SafeRefCounted` depends on DIP1000. Without it it isn't any better than the old `RefCounted` type. Surprisingly, this doesn't automatically mean it can't be done in Phobos 3. Within the present language it wouldn't be possible, but editions are going to change the calculus. In the future when DIP1000 is on or off per module as opposed to compilation unit, it is going to the client code which has to be DIP1000 for ref counting to be safe. The reference counted type can be implemented with our without DIP1000 just as well (although non-DIP1000 implementation of `borrow` would probably need a [currently missing](https://github.com/dlang/dmd/issues/20302) ability to manually inspect the scopeness of the argument function - currently the safety is inferred by calling the argument function with a `scope` argument which probably doesn't work outside DIP1000. Also ` safe`ty unit tests of it would need DIP1000.). Editions are also one more reason to do away with inspecting for DIP1000. With editions, the current definition of the inspection ```D private auto dip1000Test(int x) {return *&x;} // Used in the `static if` package(std) enum dip1000Enabled = is(typeof(&dip1000Test) : int function(int) safe); ``` is going to inspect Phobos, not the client code. While we're probably going to have a way to inspect client code instead, IMO it is a code smell if you're inspecting the edition / completion flags of your client code. What are your thoughts?
Feb 23
On Saturday, 22 February 2025 at 19:05:49 UTC, Dukc wrote:This sounded obviously a good idea back then, so the destructor of SafeRefCounted has a `static if` that checks for DIP1000 and does the deallocation inside a ` trusted` lambda if the switch is enabled, directly otherwise. (...) What are your thoughts?To me it always looked hacky, and I'd get rid of it as well. On the other hand, I think reference counting sucks in general, and see little value in using it in Phobos. Especially in functions like dirEntires, as if that is going to be called a lot in performance critical real-time code. If you want to get rid of the GC dependency, you can use arena allocation. ([I talked about this last DConf](https://dconf.org/2024/index.html#dennisk))
Feb 23
On Sunday, 23 February 2025 at 20:02:03 UTC, Dennis wrote:On the other hand, I think reference counting sucks in general, and see little value in using it in Phobos. Especially in functions like dirEntires, as if that is going to be called a lot in performance critical real-time code.I disagree, reference counting is a great tool for this use case. Not because of the memory, but because if the directory handles. Were the `dirEntries` implementation garbage collected, the directory handles would get released only when the collection happens, which could never happen if the program doesn't use much RAM. Admittedly this kind of reference counting doesn't mandate using `SafeRefCounted` - the reference counter could just free the directory handles and leave the RAM management to the GC, and this would be totally ` safe`. When I think of it, maybe `DirEntries` should have the `SafeRefCounted` field as an untyped `void[x]` field instead and have a manually written ` trusted` destructor to call the `SafeRefCounted` destructor, so the ABI of `dirEntries`/`DirEntries` isn't affected. Ironically this means there's no benefit anymore from using `SafeRefCounted` over the old `RefCounted`.
Feb 24
On Monday, 24 February 2025 at 17:18:55 UTC, Dukc wrote:On Sunday, 23 February 2025 at 20:02:03 UTC, Dennis wrote:For resource management with deterministic lifecycle, I usually prefer non-copyable structs with refs. Only in the rare case when they need to be accessible in multiple parts of the code would I resort to ref counting.On the other hand, I think reference counting sucks in general, and see little value in using it in Phobos. Especially in functions like dirEntires, as if that is going to be called a lot in performance critical real-time code.I disagree, reference counting is a great tool for this use case. Not because of the memory, but because if the directory handles. Were the `dirEntries` implementation garbage collected, the directory handles would get released only when the collection happens, which could never happen if the program doesn't use much RAM.
Feb 24
On Sunday, 23 February 2025 at 20:02:03 UTC, Dennis wrote:If you want to get rid of the GC dependency, you can use arena allocation. ([I talked about this last DConf](https://dconf.org/2024/index.html#dennisk))In general I approve of using arena allocators. RC is useful, but I'd like to avoid RC as much as possible in Phobos. We have a GC, and we're getting a vastly improved one. For cases where the GC cannot be used, almost anything is better than RC.
Feb 25
On Saturday, February 22, 2025 12:05:49 PM MST Dukc via Digitalmars-d wrote:What are your thoughts?Personally, I'm very much hoping that DIP 1000 gets killed off. There are some niche cases where it's useful, but in general, it's just too disruptive. In order to support it in general, code that doesn't care at all about it has to be marked with scope in order for the code that does care about it to be able to use that code. And the reason that the current plan is for Phobos v3 to not do anything with DIP 1000 is because a number of us feel that way. Atila is looking to see if he can come up with a way to make it work without being so disruptive, but I question that that's going to work, whereas others like Rikki are looking to do something else entirely which will theoretically cover more than DIP 1000 does. So, I don't know what we're going to end up with, but realistically, if a type requires DIP 1000 in order to be safe, then it's likely not going to be safe in Phobos v3. As for RefCounted specifically, I haven't looked at its implementation recently, so I don't know why it can't be safe, but off the top of my head, I don't see why it can't be possible to create something like RefCounted that is safe. Maybe some part of what the current design does would have to be disallowed, but it sure seems like it should be possible to have reference counting work with safe so long as you don't allow references to the internals to escape. And if for some reason, it can't be, well, then either the code using it will need to wrap it in a way that allows for safe, or it can't be safe. And for the vast majority of code, nothing like RefCounted is needed. The GC largely solves the problem with regards to memory management, and the cases where you want or need to do something else will probably want a solution that's tied to whatever allocators are being used rather than a more general ref-counting solution (assuming that reference counting is wanted at all). It's mostly just system resources that need to be deterministically freed / destroyed where reference counting becomes necessary. So, IMHO, we do need a solution for reference counting in Phobos v3, but it's also niche enough that I don't think that it's the end of the world if some trusted is required for it - much as I agree that it would ideally be safe. But again, I'd have to sit down and work through the problem to say for sure whether it can be safe without DIP 1000. Either way, as things stand, DIP 1000 is not going to be a solution for making anything in Phobos v3 safe. - Jonathan M Davis
Feb 24
On 25/02/2025 1:17 PM, Jonathan M Davis wrote:On Saturday, February 22, 2025 12:05:49 PM MST Dukc via Digitalmars-d wrote: What are your thoughts? Personally, I'm very much hoping that DIP 1000 gets killed off. There are some niche cases where it's useful, but in general, it's just too disruptive. In order to support it in general, code that doesn't care at all about it has to be marked with scope in order for the code that does care about it to be able to use that code. And the reason that the current plan is for Phobos v3 to not do anything with DIP 1000 is because a number of us feel that way. Atila is looking to see if he can come up with a way to make it work without being so disruptive, but I question that that's going to work, whereas others like Rikki are looking to do something else entirely which will theoretically cover more than DIP 1000 does. So, I don't know what we're going to end up with, but realistically, if a type requires DIP 1000 in order to be safe, then it's likely not going to be safe in Phobos v3.I'm exploring it from both directions. Trying to model more things (without doing whole program analysis), and have the default be "to give up" when it cannot analyze what it does or doesn't understand. Basically get the best of both worlds by having a fast and a slow DFA engine. It'll mean that PhobosV3 would have DFA language features, with some guarantees. There may even be whole modules that support the slow DFA, but we'll see about it as a stretch goal.
Feb 24
On Tuesday, 25 February 2025 at 00:17:19 UTC, Jonathan M Davis wrote:On Saturday, February 22, 2025 12:05:49 PM MST Dukc via Digitalmars-d wrote:I use dip1000 in two ways, firstly to avoid allocations on the heap and secondly to safely share references to objects with limited lifetime. Often both at the same time. Avoiding allocations (GC or malloc) allows the program to run faster with fewer resources. I personally value efficient and safe programs, and IMO it should be a core focus of the language. I do see challenges with dip1000's usage, but relegating it by saying its only useful for niche cases neglects a whole set of efficient programs. I understand not everyone cares about them to the same extent, but don't forget there is a chicken-and-egg problem as well: if it's relatively hard to write efficient programs, you'll tend to filter out the people who care about them.What are your thoughts?Personally, I'm very much hoping that DIP 1000 gets killed off. There are some niche cases where it's useful [...]Either way, as things stand, DIP 1000 is not going to be a solution for making anything in Phobos v3 safe.That is a real shame. That leaves implementing them either system, inefficiently or not at all.
Feb 25
On Tuesday, 25 February 2025 at 00:17:19 UTC, Jonathan M Davis wrote:As for RefCounted specifically, I haven't looked at its implementation recently, so I don't know why it can't be safe, but off the top of my head, I don't see why it can't be possible to create something like RefCounted that is safe.I had another look at it, and my conclusion is, it actually doesn't require DIP1000. DIP25 is enough! If you don't recall, and if I understand it correctly DIP25 is essentially DIP1000 limited only to `ref` and `return ref`. `scope`/`return scope` rules for pointers/slices/classes/struct/unions aren't included. It's already the default in the current language. Now, it's still possible to escape the payload with the same tricks you could escape a pointer to stack in default D. But this will also be fixed if we implement Simple Safe D. DIP1000 is not strictly needed for this use case. Although in Simple Safe D the user will likely have to make temporary copies of the payload, or parts of it, that would be unneeded in present D or with DIP1000 - just like when dealing with objects on the stack.
Feb 25
On Tuesday, 25 February 2025 at 11:46:44 UTC, Dukc wrote:If you don't recall, and if I understand it correctly DIP25 is essentially DIP1000 limited only to `ref` and `return ref`. `scope`/`return scope` rules for pointers/slices/classes/struct/unions aren't included. It's already the default in the current language.to have something akin to scoped pointers? See the postscript in the below article, where this was added as checking", and preventing escapes. https://em-tg.github.io/csborrow/ That brought to mind this discussion, and hence wondering if there are any answers there to be "stolen", since to my ignorant first glance it seems similar to what D is groping towards with DIP 1000.
Mar 05
On Wednesday, 5 March 2025 at 22:04:24 UTC, Derek Fawcus wrote:That brought to mind this discussion, and hence wondering if there are any answers there to be "stolen", since to my ignorant first glance it seems similar to what D is groping towards with DIP 1000.Yes indeed. This goes to show dip1000's aim is the right one, just needs more convenient usage. Thanks for sharing.
Mar 05
On Wednesday, 5 March 2025 at 22:27:32 UTC, Sebastiaan Koppe wrote:On Wednesday, 5 March 2025 at 22:04:24 UTC, Derek Fawcus wrote:Here is another article which touches on the same areas, and may be of interest: https://btmc.substack.com/p/borrow-checking-from-scratchThat brought to mind this discussion, and hence wondering if there are any answers there to be "stolen", since to my ignorant first glance it seems similar to what D is groping towards with DIP 1000.Yes indeed. This goes to show dip1000's aim is the right one, just needs more convenient usage. Thanks for sharing.
Mar 06
On Wednesday, 5 March 2025 at 22:04:24 UTC, Derek Fawcus wrote:On Tuesday, 25 February 2025 at 11:46:44 UTC, Dukc wrote:DIP25 is pretty similar to this, except it can also specify non-returning references. DIP1000 is more advanced, though. It works not only with `ref`erences, but also with pointers, arrays, structs and so on. Meaning you can pass a slice of a static array on the stack to a function, and it will be checked for escaping just like a `ref` equivalent neither can. Another thing DIP1000 enables over DIP25 is a `ref`erence to a scoped pointer (or slice, or struct, or class), meaning you can have a function that mutates a pointer to a local variable _in place_ . From [my post in the official blog](https://dlang.org/blog/2022/10/08/dip1000-memory-safety-in-a-modern-systems-programming-language-part-2/): ```D safe ref Exception nullify(return ref scope Exception obj) { obj = null; return obj; } safe unittest { scope obj = new Exception("Error!"); assert(obj.msg == "Error!"); obj.nullify; assert(obj is null); // Since nullify returns by ref, we can assign // to it's return value. obj.nullify = new Exception("Fail!"); assert(obj.msg == "Fail!"); } ``` . The exception `obj` points to in the unit test is allocated on the stack because of `scope`, yet `nullify` can mutate the class reference in place (as opposed to only the class itself). You can't do this safely with DIP25 alone, since you can't have a `ref` to another `ref`.If you don't recall, and if I understand it correctly DIP25 is essentially DIP1000 limited only to `ref` and `return ref`. `scope`/`return scope` rules for pointers/slices/classes/struct/unions aren't included. It's already the default in the current language.seems to have something akin to scoped pointers? See the postscript in the below article, where this was added checking", and preventing escapes. https://em-tg.github.io/csborrow/
Mar 06