As a library writer I've struggled a bit with to provide easy resource clean-up despite using class objects.

Is there reasons to use class objects that hold resources in the first place vs Unique!T or RefCounted!T structs?
I think yes:
- classes have reference semantics without naming or runtime overhead: you read Resource and not RefCounted!Resource - no need to disable the postblit or have a valid .init is another thing That's about it from the top of my head and it may not be good reasons!

As you probably know GC-called destructors enjoy a variety of limitations:
- can't allocate,
- can't access members,
- aren't guaranteed to happen at all.

This makes GC-called destructors mostly useless for non-memory resource release. IIRC Andrei suggested once that destructors shouldn't be called at all by the GC, something that I agree with.

As such, some of us started providing a release()/dispose()/close() method, and have the destructor call it to support both scoped ownership and manual release.

But that introduce accidentally correct design when the destructor is called by GC, and avoids a leak. This is arguably worse than the initial problem.

It turns out separating calls of ~this() that are called by the GC from those that are called for a deterministic reason is enough, and support all cases I can think of: Unique!T/scoped!T/.destroy/RefCounted!T/manual/GC-called

It works like this:

----------------

class MyResource
{
    void* handle;

    this()
    {
        handle = create_handle();
    }

    ~this()
    {
if (handle != null) // must support repeated calls for the case (called by .destroy + called by GC later)
        {
            ensureNotInGC("MyResource");
            free_handle(handle);
        }
    }
}

----------------

ensureNotInGC() is implemented like this:

----------------

void ensureNotInGC(string resourceName) nothrow
{
    debug
    {
        import core.exception;
        try
        {
            import core.memory;
void* p = GC.malloc(1); // not ideal since it allocates
            return;
        }
        catch(InvalidMemoryOperationError e)
        {
            import core.stdc.stdio;
fprintf(stderr, "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", resourceName.ptr);
            assert(false); // crash
        }
    }
}

--------------

Looks ugly? Yes, but it makes the GC acts as a cheap leak detector, giving accurate messages for still opened resources.


Reply via email to