Go Programming talk [OT]

Walter Bright newshound1 at digitalmars.com
Mon Jun 7 11:19:30 PDT 2010


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.


More information about the Digitalmars-d mailing list