RAIA does not, in fact, play nice with garbage-collected allocation; exposing finalization to users is fraught with peril. Hence Stu's slogan: Resource Acquisition Is Not Allocation. (Stu also strikes this pose because I am against viewing all problems as nails for the OO hammer.) What's needed is a different lexically-scoped resource acquisition scheme, one which deals with exceptions.
Stu introduces two mechanisms. The general flexible mechanism is the => statement delay operator, which causes the statement following it to be executed when the current lexical scope ends (or when an exception is thrown). So if we had a C-like file I/O library, we might write this:
somefunc(filename)
{
f := fopen(filename, "r") => fclose(f);
... do stuff ...
if [bad thing happens] then return none; endif;
...
return result;
}
Note that this assumes that if fopen() fails it returns none and fclose() accepts none simply to simplify this sort of code. (The first thing the code will actually do is 'cast away' none, since fread()/fgets() sorts of codes probably won't accept none.)
The second mechanism I want to design is to make the above more automatic. We write a single wrapper function around fopen and fclose which returns a tuple containing the filehandle and a void(void) closure that will fclose it:
fopenlex(filename, mode)
{
f := fopen(filename, mode);
return (f, func() { fclose(f); } );
}then in some other routine we can call fopenlex in some special way that causes the void(void) closure to automatically be treated the same as the =>-ified statement earlier. For example, maybe we assign it to a special operator:
somefunc(filename)
{
(f, =>) := fopenlex(filename, "r");
...
}But while => is a really cute operator for the original scenario, it's incredibly ugly here. The symbol "_" is already used to mean "ignore this". Suggestions? I'm also open to things that don't involve assignment to a magical special symbol; we could just prefix 'fopenlex()' with an operator which means 'return the first element of this tuple, and =>-ify the second element of it', although that's obviously less flexible.
September 6 2004, 11:29:30 UTC 7 years ago
if (need_file) { f := fopen("foo","r") => fclose(f) } c = fgetch(f)Second, I don't think I just want a simpler syntax for the fopenlex thing, I think I want no syntax — I'd prefer not to have to be consciously aware about resource allocation at all, if I don't want to.It seems like the C++ idiom works pretty well, in the way it ties together the scope of the resource lock with the scope of a variable. I like the idea of alternate solutions to the problem, but I don't see the big win of this one over RAII.
September 6 2004, 13:01:19 UTC 7 years ago
It does this by pushing the constructor/destructor off into the definition of a placeholder class whose existence may be entirely irrelevant except for holding the destructor. For example, in the code generator for my Stu compiler, I have a register allocator which is, for the most part, stacklike. So at the beginning of a function generating an expression, I have a call, r := allocReg();. Then at the end of the function, I have freeReg(r). r itself is an integer.
The C++ model forces me to go write a new 'AllocatedRegister' class to get a lexically scoped destructor attached to this. The => operator just allows me to write the code inline in a natural yet bug-free way: r := allocReg(); => freeReg(r);
note at end:
A subtlety of Stu syntax here is that := is not the assignment operator, it is the declare-variable-and-assign-it operator. "f = 0" assigns; "f := 0" declares and assigns. So as written, the inner f would go out of scope immediately. (I'm going to warn on nesting variables with the same name to avoid bugs where you accidentally declare without meaning to.)
Anyway, to solve this, you would just write:
if (need_file) f = fopen("foo", "r"); => if (need_file) fclose(f); c = fgetch(f);Except that to really Stu-ify this would be
if need_file then f = fopen("foo","r"); endif => if need_file then fclose(f); endif c = fgetch(f);and you could always abbreviate this to
if need_file then f = fopen("foo","r"); endif => fclose(need_file ? f : none); c = fgetch(f);So how would you do this in C++?
September 6 2004, 17:20:57 UTC 7 years ago
Using := would do the trick nicely, but you don't actually have to use it, right? What I was getting it was that the C++ idiom pretty much forces you to associate the resource object with a variable in the correct scope, which is a good thing.
I totally agree that defining an AllocatedRegister class would be a real pain compared to just using =>, but that's just a lame feature of C++'s syntax. The RAII concept itself is at least partly independent of the syntax... I think. (Hmm, is RAII exclusively a C++ thing?) Also, if you define AllocatedRegister once but use it 30 times, it starts to look more attractive.
What would rock would just be a cleaner way to define AllocatedRegister such that something along the lines of "r := AllocatedRegister()" has the same semantics as C++'s "AllocatedRegister r", but where the definition of AllocatedRegister is a trivial one-line function rather than a stupid ten-line class.
September 6 2004, 17:41:32 UTC 7 years ago