Adam Ruppe wrote:
That sucks hard. I prefer it to finally{} though, since finally
doesn't scale as well in code complexity (it'd do fine in this case,
but not if there were nested transactions), but both suck compared to
the scalable, beautiful, and *correct* elegance of D's scope guards.

I agree. D's scope statement looks fairly innocuous and one can easily pass it by with "blah, blah, another statement, blah, blah" but the more I use it the more I realize it is a

        game changer

in how one writes code. For example, here's the D1 implementation of 
std.file.read:

-------------------------------------------------------------
/********************************************
 * Read file name[], return array of bytes read.
 * Throws:
 *      FileException on error.
 */

void[] read(char[] name)
{
    DWORD numread;
    HANDLE h;

    if (useWfuncs)
    {
        wchar* namez = std.utf.toUTF16z(name);
        h = CreateFileW(namez,GENERIC_READ,FILE_SHARE_READ,null,OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
    }
    else
    {
        char* namez = toMBSz(name);
        h = CreateFileA(namez,GENERIC_READ,FILE_SHARE_READ,null,OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
    }

    if (h == INVALID_HANDLE_VALUE)
        goto err1;

    auto size = GetFileSize(h, null);
    if (size == INVALID_FILE_SIZE)
        goto err2;

    auto buf = std.gc.malloc(size);
    if (buf)
        std.gc.hasNoPointers(buf.ptr);

    if (ReadFile(h,buf.ptr,size,&numread,null) != 1)
        goto err2;

    if (numread != size)
        goto err2;

    if (!CloseHandle(h))
        goto err;

    return buf[0 .. size];

err2:
    CloseHandle(h);
err:
    delete buf;
err1:
    throw new FileException(name, GetLastError());
}
----------------------------------------------------------

Note the complex logic to recover and unwind from errors (none of the called functions throw exceptions), and the care with which this is constructed to ensure everything is done properly. Contrast this with D2's version written by Andrei:

-----------------------------------------------------------
void[] read(in char[] name, size_t upTo = size_t.max)
{
    alias TypeTuple!(GENERIC_READ,
            FILE_SHARE_READ, (SECURITY_ATTRIBUTES*).init, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
            HANDLE.init)
        defaults;
    auto h = useWfuncs
        ? CreateFileW(std.utf.toUTF16z(name), defaults)
        : CreateFileA(toMBSz(name), defaults);

    cenforce(h != INVALID_HANDLE_VALUE, name);
    scope(exit) cenforce(CloseHandle(h), name);
    auto size = GetFileSize(h, null);
    cenforce(size != INVALID_FILE_SIZE, name);
    size = min(upTo, size);
    auto buf = GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size];
    scope(failure) delete buf;

    DWORD numread = void;
    cenforce(ReadFile(h,buf.ptr, size, &numread, null) == 1
            && numread == size, name);
    return buf[0 .. size];
}
--------------------------------------------------------

The code is the same logic, but using scope it is dramatically simplified. There's not a single control flow statement in it! Furthermore, it is correct even if functions like CloseHandle throw exceptions.

Reply via email to