Future of SafeRefCounted

Dukc ajieskola at gmail.com
Sat Feb 22 19:05:49 UTC 2025


`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?


More information about the Digitalmars-d mailing list