Thread overview
should destroy!false be @system?
Feb 23, 2023
Paul Backus
Feb 24, 2023
kinke
February 23, 2023

Let's say you have a struct that manages a malloc'd resource:

struct S(T)
{
   private T* val;
   @disable this(this);
   this(T val) {
      this.val = cast(T*)malloc(T.sizeof);
      *this.val = val;
   }
   ~this() {
      free(val);
   }
   ref T get() { return *val; }
}

Now, if you just use an S!T on the stack, it works as expected.

But what happens if you destroy s? If you do, then it will call the destructor a second time when the struct goes out of scope. However, destroy will reinitialize the value with the .init value.

What about destroy!false? This doesn't initialize the value back to an .init state, and so leaves a dangling pointer in the above type.

This is a fuzzy area for me. I tend to always nullify any resource as soon as I destroy it, this way I don't accidentally screw it up later (old habit). But is that the correct expectation for a struct that has been destroyed? Is it reasonable to expect that once a struct is destroyed, it can never be used again (even to call the destructor)?

My thought is that, for a struct with a destructor:

a) the destructor should always be callable on its .init value.
b) a struct that has been destroyed where its lifetime lives on should be overwritten with its '.init' value.
c) if destroyed without overwriting with .init value, the program should make sure a second destructor call should not be allowed to happen in @safe code.

is c) reasonable? Do we need to deprecate @safe calls to destroy!false for types that have destructors?

-Steve

February 23, 2023

On Thursday, 23 February 2023 at 17:01:05 UTC, Steven Schveighoffer wrote:

>

is c) reasonable? Do we need to deprecate @safe calls to destroy!false for types that have destructors?

It doesn't actually matter if you make destroy!false @system, because the user can still call s.__dtor directly--and you can't make the destructor itself @system if you want S to be usable in @safe code.

The only way out of this, without significant language changes, is to make the destructor itself idempotent. This is what SafeRefCounted does:

https://github.com/dlang/phobos/blob/v2.102.1/std/typecons.d#L6953-L6954

February 23, 2023
On 2/23/23 12:45 PM, Paul Backus wrote:
> On Thursday, 23 February 2023 at 17:01:05 UTC, Steven Schveighoffer wrote:
>> is c) reasonable? Do we need to deprecate @safe calls to `destroy!false` for types that have destructors?
> 
> It doesn't actually matter if you make `destroy!false` `@system`, because the user can still call `s.__dtor` directly--and you can't make the destructor itself `@system` if you want `S` to be usable in `@safe` code.

If we want truly `@safe` code, then we need to define what is expected. Should a destructor be expected to be called more than once on a struct that is "live", i.e. not the `.init` value?

If so, you have to defensively mark the struct as being "destroyed", if not, you can expect that the language has either done it for you, or will not call your destructor more than once.

So which is it?

-Steve
February 24, 2023
I think it's clear that `destroy!false` should be `@system`. - Wrt. `__xdtor` (the *full* dtor, destructing the fields too, so might be `@system` even if the user dtor itself is `@safe), that would probably be ideally disallowed by the compiler as well, and ctors probably too (as they usually operate on the assumption that the payload is initialized to its `.init` value). But that's IMO way more exotic, and anything with two leading underscores in an `@safe` scope quite suspicious.