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